Chapter 17 — Application and software supply chain security: tooling
Lesson map
Lesson 17 published. Map, example, EX049.
flowchart TD
N0["Learning outcomes"]
N1["17.1 The pipeline as code"]
N2["Lab — pipeline review"]
N3["17.2 Lab result"]
N4["17.3 Fixing permissions"]
N5["17.4 Secrets and untrusted changes"]
N6["17.5 Applying across projects"]
N7["Keeping pins current"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Worked example"]
EX --> Q["Exercise EX049"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["17.1 The pipeline as code"]
N2["Lab — pipeline review"]
N3["17.2 Lab result"]
N4["17.3 Fixing permissions"]
N5["17.4 Secrets and untrusted changes"]
N6["17.5 Applying across projects"]
N7["Keeping pins current"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX049"]
[Learning outcomes]
|
v
[17.1 The pipeline as code]
|
v
[Lab — pipeline review]
|
v
[17.2 Lab result]
|
v
[17.3 Fixing permissions]
|
v
[17.4 Secrets and untrusted changes]
|
v
[17.5 Applying across projects]
|
v
[Keeping pins current]
|
v
[Worked example] --> [Exercise EX049]
Learning outcomes
You will (1) review build pipeline configuration; (2) pin pipeline actions; (3) limit token permissions; (4) protect secrets from untrusted changes.
17.1 The pipeline as code
Each project's pipeline is a configuration file in the repository. It decides which external actions run, which permissions the build token has and which secrets are available. Mistakes here affect every build. Because the configuration sits in the repository, changes to it go through the same review as code changes. The file also decides which branch can deploy, which is why the template restricts deployment to the main branch after review. Because the configuration lives in the repository, a change to it is visible in history, and reviewers can see when a permission was widened or an action changed. Diego's lab reads one such file and reports risky settings in plain language.
Lab — pipeline review
"""Check a synthetic CI configuration for risky settings."""
ci = {"steps": [{"uses": "checkout@v4"}, {"uses": "setup-node@3f2a9c1e"}, {"uses": "deploy-helper@main"}],
"secrets_on_pull_requests_from_forks": True, "token_permissions": "write-all"}
issues = []
for s in ci["steps"]:
ref = s["uses"].split("@")[1]
if not (len(ref) >= 8 and all(c in "0123456789abcdef" for c in ref)):
issues.append(f"step {s['uses']} not pinned to a commit")
if ci["secrets_on_pull_requests_from_forks"]: issues.append("secrets exposed to fork pull requests")
if ci["token_permissions"] != "read": issues.append(f"token permissions {ci['token_permissions']} (use read by default)")
for i in issues: print("-", i)
print(len(issues), "issues")
Run with python3 lab_ci.py. Verified output:
- step checkout@v4 not pinned to a commit
- step deploy-helper@main not pinned to a commit
- secrets exposed to fork pull requests
- token permissions write-all (use read by default)
4 issues
17.2 Lab result
The lab reports four issues in the synthetic pipeline: checkout@v4 and deploy-helper@main are not pinned to a commit, secrets are available to pull requests from forks, and the token has write-all permissions. Pinning to a full commit identifier means a changed action cannot silently enter the build. The lab treats any reference of at least eight hexadecimal characters as a commit, so setup-node@3f2a9c1e passes. That is looser than a full commit identifier, and Brightwave's template uses full identifiers.
17.3 Fixing permissions
The token gets read-only permission by default, with write only for the step that needs it. Write permission is granted only to the deployment step that pushes the image, after review on the main branch. The other steps can read the repository but cannot change it. Applying this across the nine pipelines that had write-all tokens is part of the three-week fix. Read-only by default means a mistaken or harmful step cannot push code or change settings in the repository.
17.4 Secrets and untrusted changes
Pull requests from outside contributors run without secrets. Deployment runs only from the main branch after review. Pull requests from forks are code written by people outside Brightwave, so running them with secrets would let an unknown change read deployment keys. Two pipelines exposed secrets this way, and both were fixed on the first day. Main-branch deployment means every change reaching a client has passed review. Contributors from outside still get test results on their pull requests, only without access to any secret.
17.5 Applying across projects
Diego runs the review across all 23 pipelines and opens fix pull requests in batches. Batching groups similar fixes, so reviewers see one kind of change at a time across several projects. The secret exposures are fixed first, unpinned actions and write-all tokens follow, and the template stops new projects repeating the same issues. The review script is the lab code extended to read every pipeline file in Brightwave's repositories. Across 23 pipelines it found 41 unpinned actions, 9 write-all tokens and 2 pipelines exposing secrets to forks. Each batch of fix pull requests is reviewed by the project owner.
Keeping pins current
Pinned actions are updated by the same weekly bot that updates libraries. Each update shows the old and new commit, and reviewers check the action's release notes before approving. This keeps the protection of pinning without leaving old versions in place forever. Diego also writes a short template pipeline with safe defaults (pinned actions, read-only token, no secrets for forks) that new projects copy, so the same mistakes are not repeated. Within two months every new project starts from the template.
Worked example
Across 23 pipelines, the review finds 41 unpinned actions, 9 write-all tokens and 2 pipelines exposing secrets to forks. All are fixed within three weeks, with the secret exposures fixed on the first day.
Synthetic case. Pipelines and counts are synthetic.
Errors and limits
Pinning needs updating too; a pinned old action can miss its own fixes.
Exercises
- EX049 — Lab issues. List the four lab issues. Rubric: 3 = two unpinned; fork secrets; write-all; 2 = three; 1 = one; 0 = none.
- EX050 — Token fix. How were token permissions fixed? Rubric: 3 = read-only default, write only where needed; 2 = partial; 1 = vague; 0 = none.
- EX051 — First fix. Which issues were fixed first and why? Rubric: 3 = secret exposure; most direct harm; 2 = partial; 1 = vague; 0 = none.
Synthesis
Treat pipeline configuration as security-critical code.
Continue studying — chapter 17 of 30
← Chapter 16 (draft) · Course chapter index · Chapter 18 (draft) →