Been thinking about writing this one for a while. Supply chain attacks against CI/CD pipelines have been picking up pace over the past two years and the March 2025 tj-actions incident was the one that finally made me sit down and document everything properly. This is how I think about hardening GitHub Actions pipelines and what I actually do in practice. Original is on my blog but happy to have the conversation here too.
On a regular Tuesday morning, your engineering team pushes code, the pipeline runs like it always does, and somewhere in those automated logs, your AWS access keys, your GitHub tokens, your RSA private keys, are being quietly printed out and collected by someone you have never met. You are not notified. No alarm goes off. GitHub does not send an email. Your pipeline shows green.
That is not a hypothetical. That is exactly what happened to over 23,000 teams in March 2025.
I have spent years working at the intersection of software engineering and security, where pipelines were moving over a billion dollars in monetary transactions, and more recently building or , where encrypted GitHub OAuth tokens are stored rather than long-lived deploy keys sitting in plain text.
3. Restrict GITHUB_TOKEN to the Minimum It Needs
Think of the GITHUB_TOKEN like a master key that GitHub automatically hands to every workflow when it starts. In many default configurations, that key has write access to your entire repository. A compromised workflow with that key can push to your main branch, publish releases, and modify your deployment configuration, all without your knowledge.
The fix is to start from zero permissions and then explicitly add back only what each job actually needs.
First, go to your repository settings under Settings → Actions → General → Workflow permissions and set it to read-only as the default.
Then in your workflow files, declare permissions explicitly:
# Lock down the whole workflow by default, nothing is permitted
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # Only needs to read code
packages: write # Needs to push a container to the registry
id-token: write # Needs to request an OIDC token
steps:
# build steps here...
deploy:
runs-on: ubuntu-latest
permissions:
contents: read # This job genuinely only needs to read code
steps:
# deploy steps here...
When you do this, even if one job in your workflow is compromised, the attacker is limited to what that specific job was permitted to do. A breach of your build job cannot automatically become a write to your main branch.
4. Treat Workflow Files Like Production Code, Because They Are
Here is something I have noticed over the years. Engineering teams will require two or three reviewers to approve any change to their core application code. But a change to a .github/workflows/ file, which controls what runs in their entire CI/CD pipeline, sometimes gets merged with a single click.
That inconsistency is a problem. Workflow files are your production infrastructure. A change to a workflow file can introduce a new third-party Action, relax permissions, or add a new deployment step. It deserves the same scrutiny as your application code.
The simplest way to enforce this is through CODEOWNERS, a GitHub feature that lets you specify which team must review changes to specific files or directories:
# .github/CODEOWNERS
# Any changes to workflow files require review from the security team
/.github/workflows/ @your-org/security-team @your-org/platform-engineering
/.github/actions/ @your-org/security-team
With this in place, no workflow change, including the introduction of a new third-party Action, can be merged without a review from someone who knows what to look for.
Additionally, use GitHub's Environments feature to add a human approval gate before any workflow deploys to production. This means even a fully compromised workflow has to stop and wait for a real human to click approve, which is often enough time to spot the anomaly.
5. Use Automated Tools to Scan Your Workflows
Manual review of workflow files is valuable but does not scale. Add automated scanning to your security toolchain so problems are caught before they ever reach your main branch.
takes a different approach. Instead of analysing your workflow files, it monitors what your workflow actually does at runtime. It can detect and block unexpected outbound network connections from your pipeline steps, the kind of monitoring that would have caught the tj-actions attack sending your secrets to an attacker's server while your pipeline was still mid-run.
The principle here is the same as application security. It is always better to fail at CI than to fail in production.
6. Mirror Critical Actions and Use a Private Container Registry
For environments where the stakes are high, such as fintech, healthcare, or critical infrastructure, depending on public GitHub Actions is an unnecessary risk. The solution is to fork the Actions you depend on into your own organisation's repositories, review the code, and pin to SHAs within your fork. That way you are not dependent on an external maintainer's account security.
For container images, which are the self-contained software packages that most modern deployments are built around, use a private registry like , a self-hosted deployment platform I have been working on quietly for a while now.
I am not ready to share the full details yet because the open source launch is coming soon, and I want to do it properly. But here is enough context to show you why these principles mattered so much to me as a builder.
The core problem Nexloy was built to solve is that small engineering teams are stuck between two painful extremes. On one side you have manual SSH scripts, scattered environment variables, and deployment processes that live only in someone's head. On the other side you have Kubernetes, which is powerful but brings enormous complexity that most small teams do not actually need. Nexloy sits in the practical middle ground: Docker Compose, Nginx, and GitHub Actions on servers you own, with a proper dashboard, deployment history, secret management, and resource bindings all in one place.
Building it meant I had to think hard about every security decision because the platform itself holds the keys to your production servers. A deployment pipeline that manages other deployment pipelines has no room for the mistakes I described earlier in this article. So the principles around encrypted credential storage, explicit deployment gates, secret redaction, and role-based access with MFA (Multi-Factor Authentication) were not optional extras I bolted on. They were the foundation.
The most important lesson I took from incidents like Trivy's compromise is this: the best security is the kind that gets generated for you, not the kind you have to remember to configure. When Nexloy creates a GitHub Actions workflow for your project, the secure patterns are already in it. The burden should not fall on every individual team to get this right from scratch every time.
I will be writing a full dedicated post about Nexloy soon, covering the architecture, the decisions behind it, and how to get started. If you want to be the first to know when it drops, follow along at , or on X at
Install Zizmor and run it against your
.github/ directory right now, before you close this tabCreate a
.github/CODEOWNERS file that routes all workflow file changes to your security team for reviewEnable . I am also building , where I am approaching 5,000 subscribers and have crossed 1 million views. Come find me. I would love to hear what you are building and what security challenges you are working through.
If any of this was useful, I write about secure DevOps, API security, and real engineering lessons from the field at , an open source self-hosted deployment platform launching soon at too. Would love to hear how your team currently handles pipeline security. Drop a comment below.
SOCIAL SHARE CARD GENERATOR