Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Getting started with GitHub Actions: CI/CD pipelines and security

  • 20 min

By updated

Improve this page

Before creating your first workflow, let us take the time to understand what a CI/CD pipeline is, how GitHub Actions works, and above all why security has to be your priority from day one.

What is a CI/CD pipeline?

Picture yourself working on an application. On every code change, you have to:

  1. Check that the code compiles and that the tests pass
  2. Build the application (an executable, a Docker image, and so on)
  3. Deploy it to an environment (staging, production, and so on)

Doing all of that by hand is slow, repetitive and error-prone. A CI/CD pipeline automates those steps.

What does CI/CD stand for?

  • CI (Continuous Integration): on every change, the code is tested and integrated automatically. Bugs surface very early.
  • CD (Continuous Delivery/Deployment): validated code is automatically prepared for, or pushed to, the target environments.

The idea is small frequent changes, validated automatically, rather than large risky releases once a month.

GitHub Actions: the engine behind your pipelines

GitHub Actions is the CI/CD service built into GitHub. When something happens on your repository (a push, a pull request, a release), GitHub Actions can run tasks automatically. Read the diagram from top to bottom: an event triggers a workflow, whose jobs run on a runner and produce a result.

An event (push, pull request, release, schedule or manual run) triggers a workflow whose Test, Build and Deploy jobs run on a runner, an Ubuntu VM; the result is a success or a failure, artifacts and a deployment.EVENTWORKFLOW, run on a runner (Ubuntu VM)RESULTPushPull RequestReleaseScheduleManualJob: Testpytest, npm testJob: Builddocker buildJob: Deploykubectl applySuccess or failureArtifactsDeployment
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Concretely:

  1. An event triggers the pipeline (you push code)
  2. A workflow defines what to do (test, build, deploy)
  3. A runner executes the tasks (a virtual machine provided by GitHub)
  4. A result comes out (success or failure, artifacts, a deployment)

Why security comes before everything else

Attacks on CI/CD pipelines are no longer a textbook case. In March 2025, the compromise of the tj-actions/changed-files action made it possible to exfiltrate the secrets of thousands of repositories within hours. The detail that matters: the attacker rewrote the tags from v1 to v45, so even the versions considered the most stable shipped the malicious code.

The problem with mutable tags

When you write actions/checkout@v4 after uses:, you are trusting a Git tag, in other words a movable label. The repository maintainer can point it elsewhere whenever they want, and nothing in your code will change.

On day 1 the @v4 tag points to commit abc123, legitimate and validated; on day 30 the tag is moved to commit xyz789, malicious; on day 31 your workflow using action@v4 runs xyz789 and has its secrets stolen, GITHUB_TOKEN included.tag moved!Day 1tag @v4Commit abc123legitimate code, tested, validatedDay 30tag @v4Commit xyz789malicious code, steals your secretsDay 31: your workflowuses: action@v4 → xyz789Secrets stolen!GITHUB_TOKEN, etc.
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Here is what can happen:

  1. You use actions/checkout@v4 in your workflow
  2. The action works perfectly for months
  3. An attacker compromises the account of the maintainer
  4. They move the tag v4 to point at malicious code
  5. Your next pipeline runs the attacker's code, without you changing anything at all

That is exactly what happened with tj-actions/changed-files.

The fix: pin by SHA

A SHA (Secure Hash Algorithm) is the cryptographic fingerprint of a commit: it is computed from the content. Unlike a tag, it is therefore immutable, since changing a single line of code produces another fingerprint, and your workflow would no longer find it.

A tag such as @v4 is a label you can peel off and stick elsewhere: mutable, hijackable. A SHA is a unique fingerprint: immutable, it identifies precise code. Example: avoid uses: actions/checkout@v4, prefer pinning to the full commit SHA.Concrete exampleTag (e.g. @v4)SHA (fingerprint)Avoid: uses: actions/checkout@v4Do:uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683Like a label stuck on a book:you can peel it off and stick it elsewhereMutable, can point to anything,easy to hijackLike a person's fingerprint:unique and impossible to forgeImmutable, identifies precise code,cryptographically safe
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %
# ❌ DANGEROUS: a mutable tag
- uses: actions/checkout@v4
# ✅ SECURE: a pinned SHA
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

The # v7.0.1 comment keeps a readable trace of the version behind the fingerprint, without which nobody could tell what is pinned. Renovate and Dependabot update those SHAs automatically and rewrite the comment along with them, so it stays accurate after every bump.

The three security reflexes

Adopt these practices from your very first workflow.

1. Pin the actions by SHA

# Always use the full SHA, never the tag
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

2. Declare minimal permissions

# By default, a workflow has FAR too many permissions
# Restrict them explicitly to what is strictly needed
permissions:
contents: read # Read-only on the code

3. Pass secrets through env:

# ❌ The secret becomes a command argument, visible in the process list
- run: curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.interne.lan/deploy
# ✅ The secret comes in through env: and never leaves the step environment
- run: curl -H "Authorization: Bearer $API_TOKEN" https://api.interne.lan/deploy
env:
API_TOKEN: ${{ secrets.API_TOKEN }}

GitHub masks the exact value of a secret in the logs, however you use it: both forms above print ***. The difference lies elsewhere, and it is real: a value interpolated into run: is written into the process command line, so anything running on the runner can read it. The env: block avoids that.

Your first workflow

Now that you understand the stakes, here is a secure workflow for a Python API:

.github/workflows/ci.yml
name: CI Pipeline
# Minimal permissions, declared at workflow level
permissions:
contents: read
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-24.04
steps:
# Actions pinned by SHA
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install the dependencies
run: pip install -r requirements.txt
- name: Run the tests
run: pytest

This workflow:

  • declares permissions: contents: read to limit what it can reach;
  • uses SHAs instead of tags for its actions;
  • does exactly what it is meant to do, testing the code.

The key concepts

Before going further, here is the essential vocabulary:

TermDefinitionExample
WorkflowA YAML file describing your pipeline.github/workflows/ci.yml
JobA group of tasks running on the same machinetest, build, deploy
StepAn individual task inside a job"Install Python", "Run pytest"
RunnerThe machine (VM) that executes the jobubuntu-24.04, windows-2025
ActionA reusable component, like a libraryactions/checkout, actions/setup-python
SHAThe unique, immutable identifier of a commit3d3c42e5aac5ba805825da76410c181273ba90b1

What you will learn in this module

This Foundations module gives you the basics to build and understand your first workflows safely, in four lessons that follow the order of the course plan. What is a workflow? covers the file structure, where workflows live and how one is built. Security: the basics turns the three reflexes above into practice: permissions, SHA pinning and protection against injections.

Managing secrets shows where to store passwords, tokens and configuration variables. Finally, the GitHub Actions Marketplace teaches you to evaluate a third-party action before adopting it, and to keep the right reflexes when running other people's code.

Key points

This opening module leaves you with the vocabulary and the reflexes that make the following guides readable:

  • what a CI/CD pipeline is, and what it really automates;
  • how GitHub Actions fits into the way you already work;
  • why security belongs in the picture from the first workflow, not later;
  • the three reflexes: pin by SHA, restrict the permissions, pass the secrets through env:.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn