A Free Security Pipeline In An Afternoon
Four layers of automated checks — pre-commit hooks, secret scanning, dependency alerts, and static analysis — wired into GitHub so the boring stuff happens on every commit. Total cost: zero.

Three articles in and you might be thinking: “this all sounds like a full-time job.” It’s not. The good news is that most of the boring, repetitive security work can (and should) be done by automated tools that run every time you commit code. You set them up once, then they nag you forever. Like a brilliantly patient colleague who notices when you’ve left your car keys in the fridge.
This article is going to be practical. We’ll build a security pipeline that:
- Catches secrets before they get committed
- Flags known vulnerabilities in your dependencies
- Spots common security anti-patterns in your code
- Runs automatically on every push
Total cost: zero. Total time: one afternoon, or less if you’re already familiar with GitHub.
The principle behind the pipeline
The whole point of automated security tooling is that you do not have to remember. You, a human, will forget. You’ll be tired, or in a hurry, or you’ll think “just this once.” The tool doesn’t get tired. It runs every single time.
We’re going to layer four tools, each catching a different class of mistake:
- Pre-commit hooks — catch stuff before it leaves your machine.
- Secret scanning — catch credentials that slip through anyway.
- Dependency scanning — flag vulnerable libraries.
- Static analysis — spot insecure code patterns.
Each layer catches what the previous one missed. None of them is perfect on its own. Together they catch the vast majority of the “oh no” moments before they become incidents.
Layer 01 · Local
Pre-commit hooks
What they are
A pre-commit hook is a little script that runs automatically just before Git accepts a commit. If the script fails, the commit is blocked. Simple as that.
What to install
The best tool here is called, helpfully, pre-commit. It’s a Python package that manages a whole family of hooks with a single config file.
Install it once:
pip install pre-commit
Then in your repo, create a file called .pre-commit-config.yaml:
.pre-commit-config.yaml YAML
repos:
-
repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: detect-private-key
-
repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks:
- id: gitleaks
Then run pre-commit install once per clone. From then on, every commit runs the checks automatically.
What this catches
- You accidentally committed a private SSH key
- You’ve got an unresolved merge conflict in a file
- You tried to commit a 200MB binary
- There’s a string in your code that looks like an AWS access key, a Stripe key, a Supabase service role key, or a few hundred other credential formats
That last one alone is worth the setup. Gitleaks is genuinely excellent at spotting secrets by their characteristic patterns.
Layer 02 · Server
Secret scanning on the server side
Pre-commit hooks only work if people actually have them installed. New contributors, automated tools, and the you-at-3am who’s bypassed the hook with --no-verify all slip past.
Server-side scanning is your safety net.
GitHub Secret Scanning
Free for public repos · included in GitHub Advanced Security for private.
If your code is on GitHub, secret scanning is largely already on. Go to Settings → Code security → Secret scanning, and make sure it’s enabled. GitHub partners with dozens of providers (Stripe, AWS, Slack, and many more) who’ll automatically revoke keys the moment they’re detected in a public repo. This has saved more companies than I can count.
GitGuardian
Generous free tier.
If you want this level of protection on private repos without paying for GitHub Advanced Security, GitGuardian’s free tier covers up to 25 developers. It installs as a GitHub app in about thirty seconds and scans every push in real time. It also scans historical commits, so it’ll catch secrets that are already in your Git history.
Cost · Setup · Catches
Cost: nothing. Setup time: three minutes. Catches: an alarming number of keys you didn’t realise were in there.
Layer 03 · Dependencies
Dependency scanning
The problem
Modern apps have hundreds of dependencies. When the AI writes npm install some-helper-library, it’s pulling in not just that one package but everything that package depends on, and everything those packages depend on. The average Node.js project has a dependency tree in the thousands.
Some of those dependencies have known security vulnerabilities. Some of them get taken over by malicious maintainers. Some of them were typosquatted from the start — “reqeusts” instead of “requests,” for example — and were never legitimate.
Dependabot
Free, built into GitHub.
If you’re on GitHub, enable Dependabot alerts immediately. Settings → Code security → Dependabot alerts. It scans your dependencies against a curated vulnerability database and opens pull requests to upgrade the vulnerable ones automatically.
There is literally no reason not to turn this on. It costs nothing, it creates pull requests rather than just nagging you, and it can genuinely save your bacon.
Add a .github/dependabot.yml to configure it:
.github/dependabot.yml YAML
version: 2 updates:
- package-ecosystem: “npm” directory: “/” schedule: interval: “weekly” open-pull-requests-limit: 10
Change the ecosystem to match your stack (pip, cargo, docker, github-actions, and so on).
Snyk
Free tier, broader coverage.
Snyk does what Dependabot does but with a wider vulnerability database, license compliance checking, and a rather nice web dashboard. The free tier is generous for small teams.
Worth adding alongside Dependabot if you want belt and braces — they catch slightly different things.
Layer 04 · Code
Static analysis
What it is
Static analysis tools read your code without running it and flag patterns that look like bugs. Modern security-focused static analysis (sometimes called SAST) is specifically trained to recognise insecure patterns: SQL injection, command injection, XSS, unsafe deserialisation, hardcoded crypto keys, and so on.
Semgrep
Free, open source, excellent.
Semgrep is my pick for vibe-coded projects because the rules are human-readable and there are pre-built rulesets specifically for common frameworks.
The easiest setup: add the Semgrep GitHub Action to your workflow. Create .github/workflows/semgrep.yml:
.github/workflows/semgrep.yml YAML
name: Semgrep on: push: branches: [main] pull_request: branches: [main]
jobs: semgrep: name: semgrep/ci runs-on: ubuntu-latest container: image: semgrep/semgrep steps: - uses: actions/checkout@v4 - run: semgrep ci --config=auto
That’s it. On every push, Semgrep runs its “auto” ruleset (which picks rules based on your language stack) and flags anything suspicious directly in the pull request.
CodeQL
Free for open source · part of GitHub Advanced Security for private.
GitHub’s own static analyser is very, very good — particularly at deep taint analysis, which is the bit of security analysis where the tool traces how untrusted input flows through your code. For open source repos it’s free. For private repos it’s part of Advanced Security, which isn’t.
If you’re open source, turn CodeQL on today. Settings → Code security → CodeQL analysis.
Putting it all together
Your GitHub repo should end up with roughly this structure of security-relevant files:
your-repo/ Tree
.github/ workflows/ semgrep.yml # Static analysis on every push dependabot.yml # Weekly dependency updates .pre-commit-config.yaml # Local hooks catching secrets/common mistakes
Plus these settings flipped on in the repo:
- Secret scanning (Settings → Code security)
- Dependabot alerts (Settings → Code security)
- Require status checks to pass before merging (Settings → Branches → Branch protection)
That last one is the one people forget. Your pipeline only helps if it actually blocks the merge when it fails. Configure your main branch so that PRs can’t be merged unless the security checks pass.
What this does NOT replace
Let’s be honest about the limits. An automated pipeline catches:
- Known vulnerabilities in dependencies — it has a database.
- Known secret patterns — it has a regex library.
- Well-known insecure code patterns — it has rules.
It does not catch:
- Business logic flaws. “Users can downgrade other users’ subscriptions” is not a pattern. It’s a flaw in your application’s rules. No tool will find it.
- Authorisation errors. Whether your
POST /api/account/{id}endpoint actually checks that the caller owns account{id}is a semantic question. Tools can’t answer it. - Novel attacks. New CVEs get added to the database after they’re disclosed. Zero-days are, by definition, unknown.
So: automate the boring bits, absolutely. But don’t mistake a green CI badge for an actually-secure app. The pipeline is the floor, not the ceiling.
A lightweight alternative if you don’t use GitHub
Most of the above assumes GitHub. If you’re on GitLab, Bitbucket, or self-hosted Git, the same shape applies:
- GitLab has most of this built in — secret detection, dependency scanning, SAST — under their security features. Some paid, some free.
- Bitbucket pairs well with Snyk and Semgrep via their pipelines.
- Self-hosted means running these tools yourself. Gitleaks, Semgrep, and Trivy (for dependency and container scanning) are all open source and easy to run in any CI system.
The tools are the same. Only the wiring changes.
A note on alert fatigue
The trap
Turn on too many tools with default settings, and within a week you’ll have 400 “critical” alerts, 90% of which are noise. After another week you’ll start ignoring them. After another week, someone will ignore a real one.
Three rules to keep this from happening:
- Tune the rules. If Semgrep keeps flagging the same pattern that you’ve confirmed is safe, suppress that specific rule for that specific file. Don’t train yourself to ignore warnings.
- Triage on schedule. Once a week, go through the Dependabot PRs and the security alerts. Merge the easy wins. Schedule work for the harder ones. Don’t let them pile up for months.
- Have a severity policy. Critical vulns in exposed code: fix within 24 hours. High vulns: within the week. Medium/low: schedule them into normal work. Write it down, stick to it.
The bottom line
A free security pipeline isn’t a replacement for thinking about security. But it is a monstrously good backstop for the kind of mistakes vibe coding is particularly prone to: the committed secret, the vulnerable dependency, the SQL query that looks fine at a glance but isn’t.
Spend one afternoon setting this up. Your future self — the one who isn’t writing a breach notification on Christmas Eve — will be very grateful.
Last in the series · Part 5 of 5
Prompt injection and the new attack surface — when the attacker isn’t targeting your code, but the AI that writes your code, or worse, the AI agent that runs inside your app. This one’s a proper rabbit hole.