पाठ 17 / 31

Hardening the CI System Itself

यह पाठ अभी हिंदी में अनुवादित नहीं है, इसलिए अंग्रेज़ी में दिखाया जा रहा है।

Lock down CI tokens, pin third-party actions to commit SHAs, and avoid dangerous triggers such as pull_request_target with untrusted code.

लेखक: · 15 साल से वेब डेवलपर

Your pipeline is production

A CI system holds the keys to everything: source code, signing keys, cloud deploy rights. Attackers know this, so the pipeline needs its own hardening. First, least-privilege tokens: in GitHub Actions, set a top-level permissions: block (for example contents: read) and grant extra scopes only to the jobs that need them. Second, pin third-party actions to a full commit SHA rather than a tag; tags can be moved to point at malicious code, as happened in real compromises of popular actions. Third, be careful with triggers: pull_request_target runs with the base repository's secrets and write token, so checking out and running code from a fork's pull request in that context hands an attacker your secrets. Fourth, avoid script injection: never interpolate untrusted values like ${{ github.event.pull_request.title }} directly inside a run: script; pass them through an environment variable. Finally, protect branches and require reviews for workflow file changes.

Finding unpinned actions

A small checker that flags uses: lines not pinned to a 40-character commit SHA. Local actions (./) are skipped. It also flags actions/checkout@v4, the tag style earlier examples use for readability; in a real repository pin those to SHAs too and let Dependabot or Renovate bump them.

import re

workflow = '''
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # full SHA of a release
      - uses: some-org/deploy-action@main
      - uses: ./.github/actions/local
'''
USES = re.compile(r"uses:\s*([^\s#]+)")
SHA = re.compile(r"@[0-9a-f]{40}$")
for ref in USES.findall(workflow):
    if ref.startswith("./"):
        continue
    status = "pinned" if SHA.search(ref) else "NOT PINNED"
    print(f"{status:<10} {ref}")

Output:

NOT PINNED actions/checkout@v4
pinned     actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
NOT PINNED some-org/deploy-action@main

Pass untrusted input through env

Instead of run: echo "${{ github.event.issue.title }}", set env: TITLE: ${{ github.event.issue.title }} and use "$TITLE" in the script. The shell then treats it as data, not as part of the script text.

A master key on a hook

Leaving deploy credentials in a CI system anyone can trigger is like hanging the hostel's master key on a hook by the entrance with a sign saying 'staff only'. The sign is not a lock.

त्वरित जाँच: Why pin a third-party GitHub Action to a full commit SHA instead of a tag like @v3?

  • SHAs make workflows run faster
  • Tags are not allowed in workflow files
  • A tag can be moved to different code, while a commit SHA always refers to the same content
  • SHAs automatically update to the latest version
Answer

A tag can be moved to different code, while a commit SHA always refers to the same content — Git tags are mutable references; a compromised maintainer account can repoint them. A full SHA is immutable.