متغيرات البيئة والأسرار

متغيرات البيئة (Environment Variables) قيم مسمّاة، مثل BASE_URL=https://staging.shop.example، يسلّمها مشغّل CI لكل عملية يبدؤها. وبها تعرف مجموعة الاختبارات الواحدة أين تعمل، وبأي حساب، وبأي إعدادات، دون أن يتغير سطر واحد من شيفرة الاختبار بين حاسوبك وبيئة staging والإنتاج. أما الأسرار (Secrets) فهي الجزء من هذه القيم الذي يمنح صلاحية وصول — كلمات المرور ومفاتيح API والرموز (tokens) — ولها مسار محمي: مخزن مشفّر، وحجب في السجلات، ووصول مقصور على المهام التي تحتاجها.

مثال ذلك: مجموعة Playwright نفسها تعمل على staging بحساب اختبار خاص بها، وبعد النشر تعمل مجموعة فرعية من اختبارات الدخان (smoke) على الإنتاج بحساب مراقبة للقراءة فقط. ولا يختلف بين التشغيلين إلا المتغيرات والأسرار.

لماذا يهم

تُنتج أخطاء الإعداد بعضاً من أكثر نتائج خط الأنابيب تضليلاً. فمتغير مفقود يرجع إلى قيمة افتراضية قد يرسل اختبارات طلبات الدمج إلى الإنتاج؛ وعنوان أساسي خاطئ يجعل التشغيل الأخضر لا يثبت شيئاً عن النسخة المختبَرة؛ وكلمة مرور جرى تدويرها تحوّل المجموعة كلها إلى الأحمر بين ليلة وضحاها، فيسجّل أحدهم عشرين عيباً في المنتج هي في الحقيقة تغيير إعداد واحد.

وتضيف الأسرار كلفة أمنية. فالرمز الذي يُودَع في المستودع، أو يُطبع في سطر تصحيح، أو يُلتقط في ملف تتبع مرفوع، يستطيع نسخه كل من يقرأ المستودع أو يفتح التشغيل؛ وكثيراً ما تصل حسابات الاختبار في البيئات المشتركة إلى أنظمة حقيقية: بيئات الدفع التجريبية ومزوّدي البريد الإلكتروني والحسابات السحابية. لهذا تخصّص ورقة OWASP المختصرة لإدارة الأسرار (Secrets Management Cheat Sheet) قسماً مستقلاً لخطوط CI/CD. والمختبِرون يكتبون هذه المهام ويشغّلونها، فهم جزء من خط الدفاع.

تذكير: الإعدادات تعيش خارج شيفرة الاختبار

وضع دليل اختبار CI/CD القواعد الأساسية: تعمل اختبارات خط الأنابيب (Pipeline) في بيئات مؤقتة حيثما أمكن، وفي بيئات مشتركة مثل staging حيث تتطلب الواقعية ذلك؛ وينشئ خط الأنابيب بيانات الاختبار بنفسه؛ وتُحفظ الأسرار في مخزن الأسرار في منصة CI أو في خزنة خارجية، محصورةً في البيئات التي تحتاجها، ومحجوبةً في السجلات، ومدوَّرةً دورياً، ولا تكون أبداً في المستودع، ولا تُطبع، ولا تدخل المخرجات (artifacts). ولا تتلقى مسارات العمل التي تطلقها طلبات الدمج من المستودعات المتفرعة (forks) أسرار المستودع افتراضياً. ويتعمق هذا الدرس خطوة أخرى: كيف تنتقل القيمة من المنصة إلى اختبار قيد التشغيل، وكيف يبقى هذا المسار صحيحاً وآمناً.

كل ما يختلف بين تشغيل وآخر أو بين هدف وآخر هو إعدادات (configuration): العنوان المختبَر، وحساب الاختبار، ومهل الانتظار، والمتصفح، والاختبارات المطلوب تشغيلها. وهذه تبقى خارج شيفرة الاختبار. تسمّي منهجية The Twelve-Factor App ذلك الفصل الصارم بين الإعدادات والشيفرة، وتقترح اختباراً سريعاً: هل يمكن جعل المستودع عاماً الآن دون كشف أي بيانات اعتماد؟ إن كان الجواب لا، فهناك شيء مكانه مخزن الأسرار يقبع في الشيفرة.

  • متغيرات الإعداد (configuration variables): إعدادات غير حساسة مثل BASE_URL وTEST_ENV وBROWSER. يجوز أن تُقرأ وتُراجَع وتُطبع.
  • الأسرار: قيم تمنح صلاحية وصول، مثل كلمة مرور حساب اختبار أو مفتاح API أو كلمة مرور قاعدة بيانات. تُخزَّن مشفّرة، وتُحجب في السجلات، ولا تُعطى إلا للمهام التي تحتاجها.
  • متغيرات المنصة: قيم تضبطها منصة CI في كل تشغيل، مثل CI=true، والإيداع (GITHUB_SHA في GitHub Actions وCI_COMMIT_SHA في GitLab)، ومعرّف التشغيل (GITHUB_RUN_ID وCI_PIPELINE_ID). أثبتها في التقارير لتشير كل نتيجة إلى النسخة والتشغيل بالضبط.

متغيرات البيئة: كيف يقرأ الاختبار إعداداته

متغير البيئة زوج من اسم وقيمة يمرّره نظام التشغيل إلى العملية عند بدئها، وترثه العمليات الفرعية. يضبط مشغّل CI المتغيرات لكل خطوة، ويرثها مشغّل الاختبارات، وتقرؤها شيفرة الاختبار: process.env.BASE_URL في JavaScript وTypeScript، وos.environ["BASE_URL"] في Python، وSystem.getenv("BASE_URL") في Java.

يمكن ضبط القيم على عدة مستويات. في GitHub Actions يعمل المفتاح env على مستوى مسار العمل والمهمة (job) والخطوة (step)، والأكثر تحديداً هو الذي يغلب: قيمة الخطوة تتقدم على قيمة المهمة. ومتغيرات الإعداد المحفوظة في إعدادات المنصة (السياق vars) توجد على مستوى المؤسسة والمستودع والبيئة؛ وقيمة البيئة تغلب قيمة المستودع، وقيمة المستودع تغلب قيمة المؤسسة. ويقدّم GitLab الفكرة نفسها بمتغيرات CI/CD في إعدادات المشروع أو المجموعة. وحين يتلقى اختبار قيمة غير متوقعة، حدّد المستوى الذي جاءت منه قبل أن تشك في المنتج.

  • كل قيمة نصّ. فالمتغير HEADLESS=false يصل بوصفه النص "false"، وهو قيمة truthy في JavaScript؛ قارن بالنص، وحوّل الأرقام بـNumber().
  • في JavaScript تكون قيمة المتغير المفقود ببساطة undefined، لا خطأً: فيستدعي الاختبار undefined/login، أو ما هو أسوأ: عنواناً احتياطياً.
  • القيم الافتراضية مقبولة للإعدادات التي لا ضرر منها مثل مهلة الانتظار، ولا تُقبل أبداً للهدف أو لبيانات الاعتماد. فغياب BASE_URL يجب أن يوقف التشغيل، لا أن يرجع إلى الإنتاج.
  • المتغيرات لا تُحجب. تنبّه وثائق GitHub إلى أن المتغيرات تظهر دون حجب في مخرجات البناء، فكلمة المرور المحفوظة متغيراً تُطبع كما هي.
// tests/config.ts: read once, check early, never print a secret
const required = ['TEST_ENV', 'BASE_URL', 'TEST_USER', 'TEST_PASSWORD'];
const missing = required.filter((name) => !process.env[name]);
if (missing.length > 0) {
  throw new Error('Missing variables: ' + missing.join(', ')); // no silent fallback
}

export const config = {
  env: process.env.TEST_ENV!,                        // staging | production
  baseUrl: process.env.BASE_URL!,
  user: process.env.TEST_USER!,
  password: process.env.TEST_PASSWORD!,             // a secret: use it, never log it
  headless: process.env.HEADLESS !== 'false',        // every value is a string
  timeoutMs: Number(process.env.TIMEOUT_MS ?? '30000'), // harmless default
  allowWrites: process.env.TEST_ENV !== 'production',
};

console.log('env=' + config.env + ' baseUrl=' + config.baseUrl); // never the password

الأسرار: التخزين والحجب ومبدأ أقل الصلاحيات

يُخزَّن السر مرة واحدة، في مخزن الأسرار في منصة CI أو في خزنة خارجية، ويُحقن في المهمة وقت التشغيل، غالباً بوصفه متغير بيئة. لا يعرض GitHub السر المحفوظ مرة أخرى أبداً؛ يمكنك استبداله فقط. ويوفر GitLab ذلك مع الخيار «Masked and hidden». والأسرار لا تدخل المستودع أبداً: لا في ملف خط الأنابيب، ولا في ملف .env مُودَع، ولا في ملف بيانات اختبار، ولا في بيئة Postman مُصدَّرة، ولا في لقطة شاشة داخل تقرير عيب. وعلى جهازك احفظها في ملف .env يستبعده .gitignore. والسر الذي وصل إلى تاريخ Git يُعدّ مسرَّباً حتى لو حذفه الإيداع التالي، لأن التاريخ يحتفظ بكل نسخة: ألغِه ودوّره أولاً، ثم نظّف.

الحجب (Masking) يستبدل القيمة الدقيقة للسر في سجل المهمة بـ*** (في GitHub) أو [MASKED] (في GitLab). إنه شبكة أمان فيها ثغرات، وكلتا المنصتين تقول إنه غير مضمون. وتطلب وثائق GitHub أن تسجّل سراً أيضاً كل قيمة محوَّلة (بترميز Base64 أو ترميز URL) وكل قيمة مشتقة، مثل رمز موقَّع بمفتاح سري، باستخدام أمر مسار العمل ::add-mask::، وتحذّر من أن السر الموضوع داخل كتلة JSON أو YAML أقل احتمالاً بكثير أن يُحجب. والحجب لا يغطي إلا السجل: ملفات التتبع (traces) وملفات HAR ولقطات الشاشة ومقاطع الفيديو وتقارير HTML التي التقطت ترويسة Authorization أو كلمة مرور مكتوبة تُرفع كما هي.

تحمل المتغيرات إعدادات يجوز لكل من لديه صلاحية أن يقرأها ويطبعها سجل المهمة؛ وتحمل الأسرار صلاحية الوصول، وتُحفظ في مخزن مشفّر، وتُحجب في السجل وحده، ويجب إبعادها عن المخرجات.

متغير إعداد

إعداد يجوز أن يُقرأ ويُراجَع ويُطبع

ما يحمله:
العنوان الأساسي، واسم البيئة، والمتصفح، ومهل الانتظار، ومفاتيح الميزات
مثال:
BASE_URL=https://staging.shop.example
أين يُحفظ:
ملف خط الأنابيب، أو ملف إعدادات مُودَع، أو متغيرات المنصة (vars)
بعد الحفظ:
يقرؤه ويراجعه كل من لديه صلاحية
في سجل المهمة:
يُطبع كما هو
في المخرجات:
كما كتبته الاختبارات
من يتلقاه:
كل مهمة في نطاقه؛ والمستوى الأكثر تحديداً يغلب
عند التغيير:
عدّل القيمة وأعد التشغيل

سر

قيمة تمنح صلاحية وصول، وتُسلَّم عبر مسار محمي

ما يحمله:
كلمات المرور، ومفاتيح API، والرموز، وكلمات مرور قواعد البيانات، ومفاتيح التوقيع
مثال:
TEST_PASSWORD=***
أين يُحفظ:
مخزن الأسرار في المنصة أو خزنة، ولا يكون في المستودع أبداً
بعد الحفظ:
للكتابة فقط في GitHub (يُستبدل ولا يُعرض)؛ وفي GitLab مع «Masked and hidden»
في سجل المهمة:
تظهر القيمة الدقيقة *** أو [MASKED]؛ وقد تفلت القيم المرمَّزة أو المشتقة من الحجب
في المخرجات:
لا يُحجب: أبعده عن ملفات التتبع وملفات HAR والتقارير
من يتلقاه:
المهام التي تحتاجه فقط: محصور في البيئة، وبموافقة للإنتاج، ولا يصل أبداً إلى طلبات الدمج من forks
عند التغيير:
دوّره وفق جدول، وفوراً بعد أي انكشاف

السلوك كما توثّقه GitHub Actions وGitLab CI/CD؛ وتستخدم منصات CI الأخرى أسماءها الخاصة للأفكار نفسها.

  • بيانات اعتماد اختبار مخصّصة لكل بيئة، بأدنى دور تحتاجه الاختبارات: لا حساب شخص بعينه أبداً، ولا حساب مسؤول «من باب الاحتياط».
  • احصر كل سر في البيئة التي تحتاجه. في GitHub Actions لا يصل سر البيئة إلا إلى المهام التي تشير إلى تلك البيئة، ومع المراجعين المطلوبين (required reviewers) لا تستطيع المهمة قراءته قبل أن يوافق أحدهم؛ ولا تصل المتغيرات المحمية (protected) في GitLab إلا إلى خطوط الأنابيب على الفروع أو الوسوم المحمية.
  • امنح رمز خط الأنابيب نفسه صلاحية القراءة فقط افتراضياً (permissions: contents: read للرمز GITHUB_TOKEN)، ووسّعها فقط في المهمة التي تحتاج إلى أكثر.
  • أبقِ على قاعدة المستودعات المتفرعة: مسارات العمل التي تطلقها طلبات الدمج من forks لا تتلقى أسرار المستودع. شغّل الاختبارات المعتمدة على الأسرار بعد الدمج بدلاً من الالتفاف على القاعدة.
  • فضّل بيانات الاعتماد قصيرة العمر على المفاتيح طويلة العمر: مع OpenID Connect (OIDC) تقدّم المهمة إلى المزوّد السحابي رمزاً تصدره منصة CI، فتتلقى بيانات اعتماد قصيرة العمر لذلك التشغيل، ولا يُخزَّن أي مفتاح سحابي سراً. ودوّر الباقي وفق جدول وفوراً بعد أي انكشاف، واحذف ما لا تستخدمه أي مهمة.

جرّبها: مولّد الأسرار العشوائية جرّبها: مرمّز Base64 جرّبها: مختبِر مصادقة API

إعدادات كل بيئة: مجموعة واحدة وأهداف متعددة

ينبغي أن تعمل المجموعة نفسها على بيئة مؤقتة لطلب الدمج وعلى staging وعلى الإنتاج دون أي تغيير في الشيفرة: الإعدادات وحدها هي التي تختلف. اتفق مع الفريق، كتابةً، على ما يتغير بين البيئات وما يجب ألا يتغير. تبقى شيفرة الاختبار والنسخة المختبَرة كما هي؛ ومن الممارسات الشائعة أن تُبنى النسخة مرة واحدة ثم تُرقّى هي نفسها عبر البيئات، فيقول النجاح على staging شيئاً عن النسخة التي تصل إلى الإنتاج.

تحمل آليتان هذه الفروق. الإعدادات غير السرية يمكن أن تكون في ملف مُودَع ومراجَع لكل بيئة (config/staging.json) أو في متغيرات على مستوى البيئة؛ أما الأسرار فلا تأتي إلا من المخزن، محصورةً في كل بيئة. في GitHub Actions توجد البيئات (environments) بمتغيراتها وأسرارها وقواعد حمايتها؛ وفي GitLab يمكن إعطاء متغير المشروع نطاق بيئة (environment scope)، أما حصر متغيرات المجموعة في بيئة فيتطلب فئة مدفوعة. وللإنتاج قواعده الخاصة: مجموعة دخان صغيرة للقراءة فقط، وحساب مراقبة مخصّص، وحارس في المجموعة يرفض تشغيل الاختبارات التي تغيّر البيانات هناك.

Setting           PR run (ephemeral)    staging                 production
----------------  --------------------  ----------------------  ----------------------
BASE_URL          localhost:3000        staging.shop.example    shop.example
Test account      seeded per run        qa-buyer (staging)      smoke-monitor (read)
Payments          mocked                provider sandbox        not exercised
Tests that run    @pr subset            full regression         @smoke only
Writes data       yes, reset per run    yes, own records        no
Secrets from      none needed           staging environment     production environment
Approval          none                  none                    required reviewer

جرّبها: مقارنة النصوص (Diff)

مثال تطبيقي: اختبارات staging وفحص دخان على الإنتاج

متجر إلكتروني ينشر من الفرع main. يريد الفريق تشغيل مجموعة الاختبارات الشاملة (end-to-end) كاملةً على staging بعد كل دمج، ثم مجموعة دخان فرعية بحساب للقراءة فقط بعد وصول الإصدار إلى الإنتاج. يعرض المقتطف أدناه مهام الاختبار؛ وقد حُذفت مهام البناء والنشر.

  • الأسماء نفسها والقيم مختلفة: vars.BASE_URL وsecrets.TEST_PASSWORD تأخذان قيمة staging أو قيمة الإنتاج بحسب environment الخاصة بالمهمة، لأن القيمة على مستوى البيئة تتقدم على قيمة المستودع التي تحمل الاسم نفسه. وشيفرة الاختبار لا تتغير.
  • يعمل مسار العمل عند الدفع (push) إلى main فقط؛ أما طلبات الدمج فتستخدم الإعداد المؤقت من الجدول السابق ولا تحتاج إلى أسرار البيئات المشتركة.
  • السطر permissions: contents: read يجعل GITHUB_TOKEN للقراءة فقط في المهمتين.
  • تنتظر مهمة الإنتاج موافقة مراجع مطلوب (تُضبط على البيئة في إعدادات المستودع)، وعندها فقط تستطيع قراءة أسرار الإنتاج؛ ثم تشغّل اختبارات @smoke بحساب للقراءة فقط، وتكون قيمة allowWrites في الإعدادات false.
  • في السجل تعرض مجموعة Run npx playwright test قيم env الخاصة بالخطوة: BASE_URL واضحاً، وTEST_PASSWORD بالشكل ***. ثم تطبع الاختبارات السطر env=staging baseUrl=https://staging.shop.example. وعندما يفشل تشغيل، تخبرك هذه الأسطر بما اختُبر فعلاً، وعليها يبني الدرس التالي.
# .github/workflows/e2e.yml (excerpt; build and deploy jobs omitted)
on:
  push:
    branches: [main]
permissions:
  contents: read                       # GITHUB_TOKEN can only read the code
jobs:
  e2e-staging:
    runs-on: ubuntu-latest
    environment: staging               # staging variables and secrets only
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
        env:
          TEST_ENV: staging
          BASE_URL: ${{ vars.BASE_URL }}                # variable: shown in logs
          TEST_USER: ${{ vars.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}   # secret: masked in logs
  smoke-production:
    needs: e2e-staging                 # full file: after the production deploy job
    runs-on: ubuntu-latest
    environment: production            # waits for a required reviewer
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --grep @smoke
        env:
          TEST_ENV: production
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ vars.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

جرّبها: منسّق YAML

أخطاء شائعة

  • إيداع سر — في ملف خط الأنابيب أو .env أو ملف بيانات اختبار أو مجموعة طلبات مُصدَّرة — ثم اعتبار الإيداع التالي الذي يحذفه إصلاحاً، بدلاً من إلغاء القيمة وتدويرها.
  • حفظ كلمة مرور اختبار متغيراً عادياً لأنها «مجرد حساب اختبار»: المتغيرات تُطبع دون حجب، وحسابات الاختبار في البيئات المشتركة كثيراً ما تصل إلى بريد إلكتروني حقيقي أو بيئات دفع تجريبية أو بيانات تشبه بيانات العملاء.
  • الثقة العمياء بالحجب: طباعة سر «للتصحيح فقط»، أو تسجيل ترويسات الطلب كاملة، أو رفع ملفات تتبع وملفات HAR تحتوي رموزاً.
  • إعطاء المتغير المفقود قيمة احتياطية تشير إلى الإنتاج أو إلى بيئة مشتركة، أو قراءة "false" على أنها صحيحة (true).
  • استخدام بيانات اعتماد واحدة واسعة الصلاحيات لكل المهام والبيئات، فيكشف تسريبٌ من مهمة طلب دمج بيئةَ الإنتاج.
  • الالتفاف على قاعدة forks بالمشغّل pull_request_target مع سحب شيفرة الـfork، وهذا يشغّل شيفرة غير موثوقة مع أسرار المستودع؛ وإرشادات الأمان في GitHub تحذّر من هذا تحديداً.

من منظور ضمان الجودة

عامل إعدادات خط الأنابيب بوصفها أساساً للاختبار (test basis) وشيفرةً تخضع للمراجعة في آن واحد. حين يتغير ملف مسار عمل، اسأل: أي بيئة تستهدف كل مهمة، وبأي حساب ودور، وأي أسرار تستطيع قراءتها، ومن يستطيع تشغيلها، ومن يوافق على الإنتاج؟ احتفظ بسجل لبيانات اعتماد الاختبار — المالك والبيئة والدور ومكان الحفظ وآخر تدوير — دون القيم أبداً. وضع مواعيد التدوير وانتهاء الصلاحية في تقويم الفريق، لأن مفتاحاً منتهي الصلاحية يحوّل المجموعة إلى الأحمر في صباح هادئ.

اختبر الأمان، لا المنتج وحده. بعد التشغيل ابحث في سجل المهمة وفي المخرجات التي نزّلتها عن قيمة سر معروفة مخصّصة للاختبار وعن شكلها بترميز Base64؛ وأي تطابق عيب في خط الأنابيب. وبعد التدوير تأكد من أن المفتاح القديم يُرفض. وحين تفشل اختبارات كثيرة معاً بالرمز 401 أو بخطأ اتصال، افحص الإعدادات قبل تسجيل عيوب في المنتج، واذكر في كل تقرير عيب البيئة والعنوان الأساسي والإيداع ومعرّف التشغيل، ولا تذكر سراً أبداً.

تمرين عملي

راجع مقتطفاً من خط أنابيب بحثاً عن مخاطر الإعدادات والأسرار

المطلوب

فتح زميل طلب دمج يضيف مسار العمل أدناه، وهو يشغّل مجموعة الاختبارات الشاملة عند كل طلب دمج وكل دفع (push). راجعه كما تراجع شيفرة اختبار: اعثر على كل مشكلة في الإعدادات والأسرار، واشرح خطر كل منها، واقترح الإصلاح.

ثم اكتب قائمة ببيانات الاعتماد التي يجب إلغاؤها وتدويرها الآن، واشرح لماذا لا يكفي إصلاح الملف وحده.

المادة
# .github/workflows/e2e.yml (proposed in a pull request)
on: [pull_request, push]
permissions: write-all
env:
  BASE_URL: ${{ vars.BASE_URL || 'https://shop.example' }}
  ADMIN_PASSWORD: Qa-Admin-2026
jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: echo "token=$(echo -n "$API_TOKEN" | base64)"
        env:
          API_TOKEN: ${{ secrets.PROD_API_TOKEN }}
      - run: npx playwright test --trace on
        env:
          TEST_PASSWORD: ${{ vars.TEST_PASSWORD }}
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: playwright-results
          path: test-results/
الخطوات
  1. اقرأ المشغّلات (triggers) والسطر permissions: من يستطيع بدء هذه المهمة، وماذا يستطيع رمزها أن يفعل؟
  2. صنّف كل قيمة في كتل env: متغير إعداد أو سر أو قيمة من المنصة، وتحقق من أن كلاً منها محفوظ حيث ينبغي لصنفه.
  3. تتبّع كل سر عبر الخطوات: هل يُطبع، أو يُحوَّل، أو يُحفظ في أحد المخرجات؟
  4. اكتب النسخة المصحّحة وقائمة بيانات الاعتماد التي يجب تدويرها.
إظهار الإجابة النموذجية
الإجابة النموذجية

صلاحيات الرمز: permissions: write-all يمنح GITHUB_TOKEN صلاحية الكتابة في كل النطاقات، ومنها شيفرة المستودع، بينما تشغّل المهمة شيفرة يستطيع طلب الدمج تغييرها (ولا تحصل على رمز للقراءة فقط إلا طلبات الدمج من forks). اضبط contents: read في الأعلى، ووسّع الصلاحية فقط في مهمة تحتاج إلى أكثر.

رجوع صامت: العامل || في BASE_URL يرسل الاختبارات إلى عنوان الإنتاج كلما غاب المتغير، ومنها اختبارات طلبات الدمج. احذف القيمة الاحتياطية، وأوقف التشغيل عند غياب BASE_URL، وامنح طلبات الدمج هدفاً مؤقتاً أو staging.

كلمة المرور ADMIN_PASSWORD مكتوبة في الملف: يقرؤها كل من يقرأ المستودع، وتبقى في تاريخ Git، وهي لحساب مسؤول. استبدلها بحساب اختبار بأقل الصلاحيات تكون كلمة مروره سراً على مستوى البيئة.

الخطوة echo … | base64 تكتب رمز الإنتاج في السجل عمداً بصيغة يمكن عكسها. والحجب غير مضمون للقيم المحوَّلة، وكل من يستطيع فتح التشغيل يمكنه فك ترميز ما يفلت منه. احذف الخطوة؛ فمهمة طلب الدمج يجب ألا تحمل رمز إنتاج أصلاً.

المتغير TEST_PASSWORD يأتي من vars، وهذه تظهر دون حجب في السجل. احفظه سراً: secrets.TEST_PASSWORD.

الخيار --trace on مع الرفع عند always() يحتفظ بملف تتبع لكل اختبار، بأفعاله وحركة الشبكة فيه، فقد تنتهي كلمات المرور المكتوبة وترويسات Authorization في مُخرَج لا يمسّه الحجب أبداً. سجّل التتبع للاختبارات الفاشلة أو عند إعادة المحاولة فقط، واجعل مدة الاحتفاظ بالمخرجات قصيرة، واستخدم بيانات اعتماد اختبار قصيرة العمر.

لا يوجد المفتاح environment:: فلا شيء يفصل أسرار staging عن أسرار الإنتاج، ولا موافقة تحمي الإنتاج. أضف environment: staging إلى هذه المهمة، وانقل فحوص الإنتاج إلى مهمة دخان مستقلة خلف مراجع مطلوب.

دوّر الآن: ADMIN_PASSWORD لأن طلب الدمج دفعه فعلاً إلى تاريخ Git، وPROD_API_TOKEN إذا عمل مسار العمل هذا ولو مرة واحدة، لأن سطر Base64 كتبه في السجل بصيغة قد لا يلتقطها الحجب، ولأن شيفرة طلب الدمج كانت تستطيع قراءته؛ ثم احذف سجلات تلك التشغيلات ومخرجاتها. إصلاح الملف يمنع التسريبات القادمة، لكنه لا يعيد القيمة المسرَّبة سراً.

أسئلة شائعة

ما الفرق بين متغير البيئة والسر؟

متغير البيئة هو الآلية: قيمة مسمّاة تتلقاها العملية. أما السر فنوع من القيم — قيمة تمنح صلاحية وصول — ومعه الطريقة المحمية لتخزينه وتسليمه: مشفّر، ولا يمكن في الغالب قراءته من الإعدادات بعد حفظه، ومحجوب في السجلات، ومقصور على مهام معيّنة. وغالباً ما يصل السر إلى الاختبار بوصفه متغير بيئة أيضاً؛ والفرق في كل ما يسبق تلك اللحظة.

هل يمكنني حفظ الأسرار في ملف .env؟

على جهازك نعم، إذا كان الملف مدرجاً في .gitignore ويحتوي قيماً مخصّصة للاختبار فقط. أودِع ملف .env.example فيه أسماء المتغيرات وقيم وهمية لا ضرر منها ليعرف الآخرون ما يجب ضبطه. أما في CI فاستخدم مخزن الأسرار في المنصة، ولا ترفع مثل هذا الملف أبداً ضمن المخرجات.

لماذا يكون السر فارغاً عندما تعمل المهمة على طلب دمج؟

غالباً بحكم التصميم. في GitHub Actions لا تتلقى مسارات العمل التي تطلقها طلبات الدمج من forks أسرار المستودع، ولا تصل أسرار البيئة إلا إلى المهام التي تشير إلى البيئة، بعد أي موافقة مطلوبة. وفي GitLab لا يصل المتغير المحمي إلا إلى خطوط الأنابيب على الفروع أو الوسوم المحمية. افحص هذه القواعد قبل تغييرها، وصمّم اختبارات طلبات الدمج بحيث لا تحتاج إلى أسرار مشتركة.

هل ينبغي أن تعمل الاختبارات الآلية على الإنتاج أصلاً؟

مجموعة دخان صغيرة للقراءة فقط بعد النشر، بحساب مخصّص منخفض الصلاحيات، وبخطوة موافقة، ودون أي اختبار يغيّر البيانات. وكل ما عدا ذلك يعمل في مرحلة أبكر. ويشرح دليل اختبار التحقق من النشر (Deployment Verification Testing) ما ينبغي أن تفحصه هذه الاختبارات.

المراجع

  • OWASP Cheat Sheet Series: Secrets ManagementOWASP Foundationكيف تُولَّد مفاتيح API والأسرار وتُخزَّن وتُدوَّر وتُبقى بعيدة عن الشيفرة والسجلات.
  • Using secrets in GitHub ActionsGitHubتخزين بيانات الاعتماد كأسرار على مستوى المستودع أو البيئة أو المؤسسة، وكيف يخفيها GitHub في السجلات، ولماذا لا تُمرَّر إلى مسارات العمل التي تُطلقها النسخ المتفرعة (forks).
  • GitHub Actions documentationGitHubكيف تُعرَّف مسارات العمل والمهام والمشغّلات والمخرجات في GitHub Actions — حيث تعمل الاختبارات الآلية عند كل دفع أو طلب دمج.
  • GitLab CI/CDGitLabكيف تُعرَّف خطوط الأنابيب والمهام والمخرجات — حيث تعمل حزمة الانحدار الآلية عند كل تغيير.

تدرّب بأدوات متقن

تنسيق YAML والتحقق من صحته بمسافات بادئة وعلامات اقتباس متسقة.

التنسيق

ترميز النصوص أو الملفات إلى Base64 (القياسي أو الآمن لعناوين URL) داخل متصفحك.

الترميز

اختبر مصادقة Bearer وBasic ومفتاح API والترويسات المخصّصة على نقطة نهاية وقارن الاستجابات المصرّح بها بغير المصرّح بها.

اختبار واجهات API

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة