Writing / 2017

Security Automation in CI: Stop Reviewing by Hand

Manual security review can't keep up with continuous delivery. How I moved secret scanning, dependency audits, SAST, and DAST into a fintech CI pipeline.

Automate the boring security checks. Put humans on the hard problems. Ship faster with fewer holes.

Classified government environments are rigid, process-heavy, approval-driven. Everything gated. Everything slow. For classified systems, that makes sense.

Fintech isn’t classified systems.

At the fintech startup we ship multiple times a day . We handle financial data, user accounts, payment flows. The attack surface is real. But if I made every deploy wait for a manual security review, we’d ship once a week. Maybe. And the devs would hate me. Rightfully so.

So I stopped pretending manual review scales and started building security into the pipeline itself.

The Problem with Manual Gates

A manual security review at a startup doing continuous delivery looks like this: a Slack message saying “hey can you look at this PR?” followed by me context-switching from whatever I’m actually working on, skimming the diff, and approving it because I’ve got six other things on fire.

That’s security theater.

Manual review is slow. It’s inconsistent. I catch different things depending on whether I’ve had coffee. It doesn’t leave an audit trail beyond a thumbs-up emoji. And it absolutely can’t cover every commit when you’re pushing code dozens of times a day.

The goal is to stop wasting security engineers’ time on things a script can catch.

What I Built: Layered Pipeline Checks

Security in a pipeline comes in layers. You want cheap, fast checks early and heavier analysis later. The key is that none of it requires a human to manually trigger.

Pre-commit: catch secrets before they leave the laptop. This is the single highest-value automation I’ve ever set up. One leaked API key in a public repo and you’re having a very bad week. At the fintech startup, dealing with financial APIs, a leaked key could mean real money walking out the door.

#!/bin/sh
## .git/hooks/pre-commit
./scripts/scan-secrets.sh || exit 1

Dead simple. Runs in under a second. Saves you from the kind of incident that makes the news.

PR checks: block the merge if it’s dirty. Static analysis, dependency audit, container scan. All automated. All required to pass. Never allow_failure: true, which turns a gate into a suggestion.

security-checks:
  script:
    - ./scripts/sast.sh
    - ./scripts/dependency-audit.sh
    - ./scripts/container-scan.sh
  allow_failure: false

Cyber-defense exercises drill in the idea of “deny by default.” Same principle here. The merge doesn’t happen unless the checks are green. No exceptions, no “I’ll fix it later” PRs.

Post-merge: the heavy stuff. DAST against staging. Security-focused integration tests. Things like “can an unauthenticated user hit this admin endpoint?” and “are the CORS headers actually set right?” Static analysis can’t catch these. You need a running application.

In production, runtime monitoring and log analysis close the loop. Security becomes a continuous signal instead of a checkbox someone ticked three sprints ago.

Making It Stick

Most teams screw up here: they turn on everything at once, get 400 findings on the first run, and the entire dev team starts ignoring the output within a week.

Don’t do that.

Start with one check. Make it reliable. Make the failure messages useful. Instead of “vulnerability found”, say “this dependency has a known RCE, upgrade to version X, here’s the CVE link.” The difference between a useful pipeline and an annoying one is whether the developer knows what to do when it fails.

At the fintech startup I rolled checks out one at a time over a few months. Secret scanning first. Then dependency auditing. Then SAST. Each one tuned, false positives suppressed, team trained on the output before adding the next. Boring, and effective.

Build a suppression process too. Some findings are false positives. Some are accepted risks. That’s fine. But track the suppressions and review them. A suppression list that only grows is a red flag.

Humans on the Hard Problems

Scanners catch known patterns. SQL injection signatures, outdated libraries, hardcoded credentials. Important stuff. But they can’t think.

Threat modeling is a human job. So is reviewing the authentication architecture of a new feature, and penetration testing that simulates an attacker’s creativity. Incident response when something real happens at 2 AM is very much a human job.

The whole point of automating the repetitive checks is to free up time for this work. In cyber-defense exercises, dedicated teams handle threat analysis without getting bogged down running vulnerability scanners by hand. Same principle at a startup, except the “dedicated team” is me and maybe one other person. Which is exactly why automation matters even more.

The Payoff

Before automation, security at the fintech startup was me trying to review everything and inevitably missing things. After, every single commit gets checked. Every dependency gets audited. Every container gets scanned. And I spend my time on architecture reviews and threat modeling instead of eyeballing diffs.

The pipeline doesn’t get tired. It doesn’t get distracted. It doesn’t approve a PR because it’s Friday afternoon and everyone wants to go home.