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:
- Check that the code compiles and that the tests pass
- Build the application (an executable, a Docker image, and so on)
- 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.
Concretely:
- An event triggers the pipeline (you push code)
- A workflow defines what to do (test, build, deploy)
- A runner executes the tasks (a virtual machine provided by GitHub)
- 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.
Here is what can happen:
- You use
actions/checkout@v4in your workflow - The action works perfectly for months
- An attacker compromises the account of the maintainer
- They move the tag
v4to point at malicious code - 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.
# ❌ DANGEROUS: a mutable tag- uses: actions/checkout@v4
# ✅ SECURE: a pinned SHA- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1The # 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.12. Declare minimal permissions
# By default, a workflow has FAR too many permissions# Restrict them explicitly to what is strictly neededpermissions: contents: read # Read-only on the code3. 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:
name: CI Pipeline
# Minimal permissions, declared at workflow levelpermissions: 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: pytestThis workflow:
- declares
permissions: contents: readto 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:
| Term | Definition | Example |
|---|---|---|
| Workflow | A YAML file describing your pipeline | .github/workflows/ci.yml |
| Job | A group of tasks running on the same machine | test, build, deploy |
| Step | An individual task inside a job | "Install Python", "Run pytest" |
| Runner | The machine (VM) that executes the job | ubuntu-24.04, windows-2025 |
| Action | A reusable component, like a library | actions/checkout, actions/setup-python |
| SHA | The unique, immutable identifier of a commit | 3d3c42e5aac5ba805825da76410c181273ba90b1 |
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:.