A beginner-friendly guide to building an automated security pipeline with GitHub Actions — from zero, in under an hour.
Here is a scenario that plays out constantly in the world of AI-assisted development.
You build something. It works. You are excited. It is late. You run through a mental checklist — or most of it — and you push to production. Three items got checked. Two did not. You tell yourself you will fix it tomorrow.
Tomorrow, you are already building the next thing.
This is not a discipline problem. This is a systems problem. Checklists depend on human memory and human energy, and both of those fail under shipping pressure. Automation does not forget. It does not get tired. It does not skip the last two items because it is midnight and the feature finally works.
That is the entire argument for automated security gates. Not that you are careless — but that no human is perfectly consistent, and consistency is exactly what security requires.
If you read the first article in this series about vibe coding and security, you saw the Moltbook breach — a weekend-built app that exposed 1.5 million API tokens and 35,000 emails because a few security defaults were never configured. The checklist at the end of that article is a good start. This article is about making that checklist run automatically, every single time you push code, without you having to think about it.
One of the tools that makes this possible is GitHub Actions.
What Is GitHub Actions, Really?
If you have a GitHub repository — a place where your code lives online — GitHub Actions is a system that lets you run automatic tasks whenever something happens in that repository.
Something happens could mean:
- You push new code
- You open a pull request
- You merge into your main branch
- You create a new release
When that trigger fires, GitHub spins up a temporary computer in the cloud, runs whatever instructions you give it, and reports back whether everything passed or failed. You do not manage that computer. You do not pay for it beyond GitHub's free tier limits. It just runs.
Those instructions live in a file called a workflow — a plain text file written in a format called YAML that lives inside your repository at .github/workflows/. You can have as many workflow files as you want, each doing different things.
Here is the simplest possible workflow to make this concrete:
# .github/workflows/hello.yml
name: My First Workflow
on:
push: # Run this whenever code is pushed
branches: [main]
jobs:
say-hello:
runs-on: ubuntu-latest # Use a fresh Linux computer
steps:
- name: Print a message
run: echo "Code was pushed. The pipeline is running."
That is it. A name, a trigger, a machine to run on, and steps to execute. Everything we build in this article follows exactly that same structure — we are just swapping echo "hello" for real security tools.
Why This Matters More for Vibe Coders
Traditional developers have something vibe coders are still building: years of conditioned habits. The reflex to check for exposed secrets before pushing. The instinct to verify that a new endpoint requires authentication. The muscle memory of running a linter before committing.
Those habits took years to form. AI-assisted development is compressing timelines so fast that many people are shipping production apps before those habits exist.
Automated security gates close that gap. They are not a replacement for understanding security — but they are a safety net that catches common mistakes before they reach production. Think of it less like a replacement for a trained developer and more like spell-check. Spell-check does not make you a better writer, but it catches the embarrassing errors while you focus on the ideas.
The goal of this article is to give you a working security pipeline by the end. Not a theoretical one. An actual .yml file you can drop into any project today.
How to Set It Up: From Zero
You need two things before anything else:
A GitHub repository. If your project is not on GitHub yet, create a free account at github.com and push your code there. GitHub has a beginner guide for this if you have never done it.
A .github/workflows/ folder in your repository. You can create this directly on GitHub by clicking "Add file" inside your repository, or in your code editor locally. GitHub Actions picks up any .yml file inside that folder automatically.
That is genuinely all the setup required. No servers, no accounts, no credit cards.
Building the Pipeline: Layer by Layer
We are going to build one workflow file that runs three security checks in sequence. Each check catches a different category of problem. You can start with just one and add the others when you are ready — the pipeline is modular by design.
Here is the full picture of what we are building:
On every push to main:
│
├── Step 1: Gitleaks — scan for exposed secrets and API keys
├── Step 2: Semgrep — scan for insecure code patterns
└── Step 3: Snyk or CodeQL — scan for vulnerable dependencies
Each scan catches a different category of problem — secrets, insecure code patterns, and vulnerable dependencies. If any one fails, GitHub stops the pipeline before the code reaches production. You get an email, a red badge on your repository, and a detailed report showing exactly what was found and where.
The diagram below maps how the pieces connect. Think of it as your security assembly line — every push goes through it automatically, without you having to remember to run anything manually.
Let's build each layer.
Step 1: Catching Exposed Secrets with Gitleaks
Gitleaks scans your codebase for anything that looks like a secret — API keys, database passwords, tokens, private keys. It knows the patterns for hundreds of services: AWS, OpenAI, Stripe, Supabase, GitHub itself.
This is the Moltbook failure in automated form. If Gitleaks had been running on that repository, the exposed Supabase key would have been flagged before the first commit ever reached production.
Create a file at .github/workflows/security.yml and add this:
name: Security Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
secret-scan:
name: Scan for Exposed Secrets
runs-on: ubuntu-latest
steps:
# Step 1: Download your code onto the pipeline machine
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Scan the full git history, not just the latest commit
# Step 2: Run Gitleaks
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub provides this automatically
What this does in plain English: Every time you push to main or open a pull request, GitHub downloads your code onto a fresh machine and runs Gitleaks across every file and every commit in your history. If it finds anything that looks like a secret, the pipeline fails and you get a detailed report showing exactly which file and which line contains the problem.
The fetch-depth: 0 line is important — without it, Gitleaks only scans your most recent commit. With it, it scans your entire history, which catches secrets that were committed weeks ago and never removed.
Step 2: Catching Insecure Code Patterns with Semgrep
Gitleaks catches secrets. Semgrep catches insecure patterns — things like SQL queries that trust user input directly, authentication checks that can be bypassed, or configuration values that should never be public.
Semgrep has a free tier and a library of pre-written rules maintained by security researchers. You do not need to write the rules yourself. You just point it at a ruleset and it does the work.
Add this job to the same security.yml file, underneath the secret-scan job:
code-scan:
name: Scan for Insecure Code Patterns
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/javascript
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
What this does in plain English: Semgrep reads through your code looking for patterns that security researchers have identified as dangerous. The p/security-audit ruleset covers common vulnerabilities. The p/javascript ruleset adds JavaScript and TypeScript-specific checks. If your project uses Python, swap p/javascript for p/python. If it uses both, list both.
To get your SEMGREP_APP_TOKEN, create a free account at semgrep.dev, go to Settings, and copy your token. Then in your GitHub repository, go to Settings → Secrets and Variables → Actions → New Repository Secret, and paste it there as SEMGREP_APP_TOKEN. GitHub stores it encrypted and injects it into the pipeline automatically — your token never appears in your code.
Step 3: Catching Vulnerable Dependencies
Your app almost certainly uses packages written by other people — React, Express, Axios, whichever libraries your AI reached for. Those packages sometimes contain known security vulnerabilities. You did not write the vulnerability, but if it is in your app, it is your problem.
You have two solid options here depending on your comfort level.
Option A: Snyk (beginner-friendly, generous free tier)
Snyk is built for accessibility. It has a VS Code extension, a web dashboard, and a GitHub integration that makes setup straightforward. Add this job to your workflow:
dependency-scan-snyk:
name: Scan Dependencies with Snyk
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high # Only fail on high and critical issues
Get your SNYK_TOKEN by creating a free account at snyk.io, going to Account Settings, and copying your Auth Token. Add it to GitHub Secrets the same way you added the Semgrep token.
Option B: CodeQL (built into GitHub, no extra account needed)
CodeQL is GitHub's own static analysis engine. It is more powerful than Snyk for code-level analysis, slightly more complex to configure, but requires no external account. Replace the Snyk job with this:
dependency-scan-codeql:
name: Scan with CodeQL
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript # Change to python, ruby, etc. if needed
- name: Perform Analysis
uses: github/codeql-action/analyze@v3
Results from CodeQL appear directly in your GitHub repository under the Security tab — no external dashboard needed.
Which should you choose? Start with Snyk if you want immediate, readable results in plain English. Move to CodeQL when you want deeper analysis integrated directly into GitHub. There is no wrong answer — running either is infinitely better than running neither.
The Complete Pipeline
Here is the full security.yml file with all three jobs combined. Drop this into .github/workflows/security.yml in any project and your automated security gate is live:
name: Security Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
secret-scan:
name: Scan for Exposed Secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
code-scan:
name: Scan for Insecure Code Patterns
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/javascript
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
dependency-scan:
name: Scan Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
Three jobs. Three categories of risk. All running automatically on every push.
Reading the Results
When a pipeline run completes, GitHub shows you one of two things:
Green checkmarks — all scans passed. You are clear to merge or deploy.
A red X — something was found. Click into the failed job, read the output, and it will tell you exactly what was flagged, in which file, and often why it is a problem.
Do not treat a red X as a failure. Treat it as the system working. The pipeline caught something before it reached production — which is precisely what it is there to do.
One Thing to Do Right Now
If setting up all three jobs at once feels like too much, start with just Gitleaks. Create the .github/workflows/security.yml file with only the secret-scan job. Push it to your repository. Watch it run.
Once you have seen a pipeline run — green or red — the rest becomes much less intimidating. The structure is always the same: trigger, machine, steps. You are just swapping in different tools.
Security automation is not a one-time setup. It is a habit that compounds. Every project you start from now on gets the pipeline from day one. Every collaborator who opens a pull request gets their code scanned automatically. Every push gets checked before it ships.
That is what consistency looks like when you remove the human from the loop.
Where This Fits in Your Stack
This pipeline covers the code layer — what is in your repository. Combined with the platform-level defaults from the previous article — RLS enabled, rate limiting on, secrets in the right environment variables — you now have security running at two levels simultaneously:
Before the code ships → GitHub Actions catches it in the pipeline
After the code ships → Platform defaults limit the damage if something slips through
Neither layer is perfect on its own. Together they cover the vast majority of the failures that appear in real-world breaches — including the ones that made Moltbook a case study.
You did not need a Computer Science degree to set this up. You needed a .github/workflows/ folder and about an hour.
The next article in this series covers something slightly different: what to do when the AI itself is part of your pipeline — and the new attack surface that creates.
Found this useful? Share it with someone who just shipped their first repo. The best time to add a security pipeline is before the first breach. The second best time is right now.

Top comments (0)