Module 2: Anatomy Of A Github Actions Workflow
5. Reusable Actions: `uses`, `with`, and the supply-chain risk
Description
Lesson 2 showed you uses: actions/checkout@v4 without stopping on an important question: what exactly is an "Action"? This lesson answers that, and does something more: it shows you that actions/checkout@v4 isn't a magic reference, it's a reference to a Git tag, a pointer that —unlike a commit— can move. You're going to run the same Action pinned by exact SHA instead of by tag, with act, and you're going to understand why that difference is a real security practice, not a formality.
Connection to the module
This lesson closes out the module's conceptual dissection (lessons 2 through 5). Lessons 6, 7, and 8 are hands-on: you're going to use actions/checkout@v4 again, now as part of Andes Cargo's first real workflow. Module 3 introduces a second Action with the same versioning logic: hashicorp/setup-terraform@v3.
Analogy: the exact recipe on page 42, or the "latest edition" of a book that gets reprinted
Asking for uses: actions/checkout@v4 is like asking someone "bring me the soup recipe from the latest edition of the cookbook" — it works, it almost always gives the right result, but if the author publishes a reprint with a corrected recipe under the same "current edition" label, your next run uses a different recipe than yesterday's, without you having changed a single line of your own file. Asking for uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 is, instead, asking "bring me exactly the recipe on page 42 of the edition from such-and-such date, no matter what the back cover says today" — a pointer that always points to the exact same content, forever, with no surprises.
What an Action is
An Action is packaged, reusable code —typically in JavaScript or as a Docker image— published in a GitHub repository, ready to use from any workflow's uses: field. The GitHub Marketplace is the public catalog of thousands of Actions of this kind, maintained both by GitHub itself (actions/checkout, actions/upload-artifact) and by third parties (hashicorp/setup-terraform, which you're going to use starting in Module 3).
The general syntax:
- uses: <organization>/<repository>@<reference>
with:
<parameter-1>: <value-1>
<parameter-2>: <value-2>
<organization>/<repository> identifies which GitHub repository the code comes from; <reference> is the part this lesson dissects —it can be a tag (v4), a branch (main), or a full commit SHA; with: passes input parameters, exactly like a function's arguments.
Tag vs. SHA: the same code, two ways of pointing to it
A Git tag like v4 isn't the code itself — it's a label, a pointer the repository's maintainer assigns to a specific commit. That pointer can be reassigned: the same v4 that today points at commit 11bd719... could, technically, point at a different commit tomorrow, if the maintainer decides to move the tag —something most serious maintainers don't do for already-published versions, but that Git's mechanism allows with no technical restriction whatsoever. A commit SHA, on the other hand, is a cryptographic fingerprint of that commit's exact content: it's mathematically impossible for two different commits to have the same complete SHA, and once that commit exists, its SHA never changes.
I ran the same actions/checkout two different ways, so you could confirm it with your own eyes:
By tag (the one you already used in lesson 2):
- uses: actions/checkout@v4
By exact SHA (the same v4.2.2, resolved to its real commit — verified against GitHub's API on the day this lesson was written):
- name: Check out, pinned to an exact commit SHA
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
act push -j checkout-by-sha
What to expect (literal output, executed to write this lesson):
[sha-pin-demo/checkout-by-sha] ⭐ Run Set up job
[sha-pin-demo/checkout-by-sha] 🚀 Start image=catthehacker/ubuntu:act-latest
[sha-pin-demo/checkout-by-sha] ✅ Success - Set up job
[sha-pin-demo/checkout-by-sha] ⭐ Run Main Check out, pinned to an exact commit SHA
[sha-pin-demo/checkout-by-sha] 🐳 docker cp src=/path/to/your/lab/. dst=/path/to/your/lab
[sha-pin-demo/checkout-by-sha] ✅ Success - Main Check out, pinned to an exact commit SHA [15.767667ms]
[sha-pin-demo/checkout-by-sha] ⭐ Run Main echo "Checked out using a SHA-pinned Action, not a moving tag"
[sha-pin-demo/checkout-by-sha] | Checked out using a SHA-pinned Action, not a moving tag
[sha-pin-demo/checkout-by-sha] ✅ Success - Main echo "Checked out using a SHA-pinned Action, not a moving tag" [52.87675ms]
[sha-pin-demo/checkout-by-sha] 🏁 Job succeeded
It runs exactly the same — same result, same behavior, because today, right now, v4 and the SHA 11bd719... point at the exact same code. That is precisely the nature of the risk this lesson names: today there's no observable difference between the two — the difference would only show up the day someone reassigned the v4 tag to a different commit, something no run today can prove to you, by definition. Pinning by SHA isn't a performance or functionality improvement — it's a guarantee about the future: whatever happens to the v4 tag from here on, your workflow keeps using exactly this commit, forever.
Why this is a security practice, not just a reproducibility one
Pinning by SHA protects against two real, industry-documented scenarios:
- A legitimate maintainer makes a mistake and publishes a broken version under an existing tag —rare, but it has happened— silently breaking every workflow that trusted that tag.
- A supply-chain attack: if a maintainer's account gets compromised, whoever has access can reassign a public tag (like
v4) to a malicious commit, with no workflow using@v4noticing the change — the next run across thousands of repositories would execute code nobody reviewed. A fixed SHA makes that attack, specifically, have zero effect on your pipeline: you'd keep pointing at the original commit, no matter what happens to the tag.
This is exactly what this ecosystem's market audit flags as the most-cited security gap —alongside the long-lived-credentials antipattern you see in Module 4—: no competing curriculum mentions SHA pinning as a practice. This guide names it here, with the mechanism shown and really run, not just mentioned in the abstract.
What this lesson does NOT build: a complete supply-chain management system —signature verification, SBOM (Software Bill of Materials), automated Action dependency scanning, tools like Dependabot configured to update pinned SHAs automatically. That's content for cloud-security-and-guardrails-guide, linked and not rewritten here. This lesson leaves you with the mechanism (uses: org/repo@SHA instead of @tag) and the why, ready to apply — the topic's full depth lives in that sister guide.
Going deeper: hashicorp/setup-terraform@v3, the Action you're going to use starting in Module 3
Module 3 introduces a second Action with the same versioning logic, this time from HashiCorp:
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.5"
Verified against GitHub's API on the day this lesson was written, hashicorp/setup-terraform's v3 tag resolves to SHA b9cd54a3c349d3f38e8881555d616ced269862dd. This lesson's same principle applies unchanged: @v3 is more readable and is what you're going to see in most of HashiCorp's official documentation examples (which is why this guide uses it that way in Module 3, prioritizing pedagogical readability over maximum possible hardening), but a team with higher security requirements would pin that Action, too, by SHA — with exactly the same syntax you already practiced above.
Common mistakes
Thinking pinning by SHA is "slower" or "worse performance" (conceptual). What happens: someone avoids SHA pinning assuming resolving a full SHA is more costly than resolving a short tag like v4. Why it happens: a SHA looks "longer and more complicated" than a three-character tag. How to spot it: if your reason for not pinning by SHA is about speed, not readability. How to fix it: as you saw in this lesson's real run, there's no performance difference at all — Git resolves both references (tag or SHA) to the same commit, at the same cost. The only real downside of a SHA is that it's less readable at a glance in the YAML —a readability cost, not a performance one.
Pinning by SHA and never updating the Action again (overcorrection). What happens: a team pins every Action by SHA, congratulating itself on the security gained, and never again checks whether those versions have their own security updates. Why it happens: pinning feels like "done" — configured once, set forever. How to spot it: if your repository has Actions pinned by SHA from more than a year ago, with no process reviewing them. How to fix it: pinning by SHA protects against unauthorized changes to an existing tag — it doesn't replace the process of deliberately updating when a new, necessary version exists (for example, a security patch from the Action itself). Tools like Dependabot can automate that process, opening a PR when a newer SHA exists for a tag — a topic for cloud-security-and-guardrails-guide, named here, not built.
Copying a SHA from a tutorial without verifying it against the real repository (security-based, the mistake this lesson specifically avoids). What happens: someone copies a SHA from a blog post or another guide without confirming that SHA really corresponds to the tag it claims to represent. Why it happens: a SHA "looks" equally trustworthy whether it's verified or not —forty hexadecimal characters tell the human eye nothing about their origin. How to spot it: if you can't explain where a specific SHA in your workflow came from. How to fix it: this lesson's two SHAs were verified against GitHub's real API (api.github.com/repos/<org>/<repo>/git/refs/tags/<tag>) on the same day this lesson was written — the same mechanism you should use before pinning any Action in a real project, instead of trusting a SHA copied from a source you didn't verify.
Exercises
Exercise 1 — Explain the difference to a colleague who's never used Git beyond the basics. Without using the word "pointer," explain in two sentences why a Git tag can "change its mind" about which commit it points at, and a SHA can't.
See solution
A complete answer sounds, roughly, like this: "A tag is like a labeled sticker stuck on a box — someone can peel it off and stick it on a different box later, and the sticker would still say the same thing even though the contents changed. A SHA is like the unique barcode, engraved into the box itself: it can't be peeled off or reassigned, because it's a mathematical property of that box's exact contents, not an external label someone put there."
Exercise 2 — Verify a SHA yourself. Without looking at this lesson, what command or URL would you use to confirm whether the SHA 11bd71901bbe5b1630ceea73d27597364c9af683 really corresponds to actions/checkout's v4.2.2 tag, instead of trusting that this lesson says so?
See solution
The most direct way is querying GitHub's public API: https://api.github.com/repos/actions/checkout/git/refs/tags/v4.2.2 (with curl or simply pasting the URL into a browser) — the response includes the object.sha field, which should exactly match the SHA you're verifying. It's, literally, the same command used to verify this lesson's two SHAs before publishing it.
Exercise 3 — Decide the right level of rigor for two different scenarios. A colleague asks whether they should pin absolutely every Action by SHA in a personal practice workflow, with no sensitive data involved. What would you tell them, contrasting that case with a pipeline that actually deploys real production infrastructure?
See solution
A complete answer sounds, roughly, like this: "For a personal practice workflow, with no real credentials or production infrastructure involved, using @v4 is perfectly reasonable — the real risk is low, and the tag's readability helps while you're learning. For a pipeline that actually touches production infrastructure, with real credentials (even short-lived ones via OIDC, as you see in Module 4), pinning every third-party Action by SHA is a serious security practice, exactly because of the supply-chain scenario this lesson names: nobody should be able to change what code runs in your production pipeline without you explicitly deciding to, by reviewing a new SHA."
Summary and next step
In this lesson you ran, with act, the same actions/checkout referenced two different ways —by tag (@v4) and by exact SHA (@11bd719...)— and confirmed both run identically today, precisely because the tag's risk isn't about the present but about the future: a tag can be reassigned, a SHA can't. You saw why this is the most-cited-as-absent security practice in the market competition motivating this guide, with the real mechanism, verified against GitHub's API, not just mentioned.
Before moving on you should be able to: explain the difference between pinning by tag and by SHA without literally using the word "pointer"; verify a SHA yourself against GitHub's API; and decide, based on context, when a SHA's rigor is worth the readability cost.
With the complete anatomy covered —on, jobs, steps, runs-on, uses, with, env, and the four triggers— lessons 6, 7, and 8 are pure hands-on. Lesson 6 teaches you to simulate any event without needing a real GitHub account, by writing your own pr-event.json.
Resources
- GitHub Docs — Finding and customizing actions — official documentation on discovering and using Marketplace Actions.
- GitHub Docs — Security hardening for GitHub Actions — the official source for the recommendation to pin third-party Actions by full commit SHA.
- GitHub — actions/checkout — the real repository whose tags and SHAs were verified for this lesson.
- GitHub — hashicorp/setup-terraform — the Action you're going to use starting in Module 3, with the same versioning mechanism.
cloud-security-and-guardrails-guide(NIEVA) — supply chain in depth: SBOM, commit signing, Dependabot, automated scanning — not built here, only named.