DevSecOps gets pitched as a culture shift with a platform attached. For a team of five engineers, that pitch misses the point. You don’t have a security department to shift left from. You have a repo, a CI pipeline and a cloud account, and the goal is to make the safe path the easy one inside those three places.

This post is a starting setup. Everything in it is free or cheap, and most of it is a configuration change rather than a project.

Start with the frame, not the tools

NIST’s Secure Software Development Framework (SP 800-218, usually called the SSDF) is a good map, and it’s short enough to skim in an afternoon. It splits the work into four groups:

  • Prepare the organization: decide who does what and set up your tooling.
  • Protect the software: keep your code, build system and releases from being tampered with.
  • Produce well-secured software: design, review and test with security in mind.
  • Respond to vulnerabilities: find, fix and learn from problems after release.

You won’t do all of it. But it’s useful for spotting gaps. A lot of small teams scan their code and do nothing to protect the pipeline that builds it, or the other way around.

Lock down the repo first

Your source repo is where every other control lives, so start there.

  • Require pull requests on the main branch, with at least one review before merge. GitHub, GitLab and Bitbucket all support this as a branch rule.
  • Turn on MFA or passkeys for everyone with write access, and for the organization owners in particular.
  • Block secrets before they’re pushed. GitHub’s push protection stops commits that contain recognizable credentials. If you’re elsewhere, a pre-commit hook running gitleaks does a similar job.
  • Keep a CODEOWNERS file for the sensitive paths: auth code, infrastructure definitions and the CI config itself.

That last one matters more than it looks. If anyone can edit the pipeline definition in a pull request, they can change what the pipeline does with your deploy credentials.

Put three checks in CI

You don’t need a dozen scanners. Three cover most of what a small team will run into:

  1. Dependency scanning. Dependabot or Renovate opens pull requests when a package you use has a known vulnerability. Commit your lockfile so builds are reproducible, and review major version bumps by hand.
  2. Static analysis. Semgrep and GitHub’s CodeQL both have free tiers and catch common mistakes like injection and unsafe deserialization. Start with the default rulesets and only turn on more once the noise is under control.
  3. Secrets scanning. Push protection catches new secrets. A scheduled scan of the full history catches the old ones that are still sitting in a commit from two years ago.

Make the first two block merges only for high-severity findings at the start. A pipeline that fails on every warning gets bypassed, and then it isn’t protecting anything.

Protect the pipeline itself

The build system is a target in its own right. In March 2025, the popular tj-actions/changed-files GitHub Action was compromised and modified to print CI secrets into workflow logs. Any repository that referenced it by a moving tag pulled the bad version automatically.

A few habits make that kind of attack much less likely to hurt you:

  • Pin third-party actions to a full commit SHA, not a tag like v4. Dependabot can keep the pins up to date for you.
  • Give workflow tokens the least access they need. Set permissions: at the top of each workflow and grant write access only where a job really needs it.
  • Drop long-lived cloud keys from CI. AWS, Azure and Google Cloud all support OpenID Connect federation from GitHub Actions and GitLab CI, so a job can get short-lived credentials for one run.

Here’s what a tightened workflow header looks like:

permissions:
  contents: read
  id-token: write   # only for the job that deploys

steps:
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2

If you want a scorecard for this, the OpenSSF Scorecard project runs automated checks against a repo and tells you which of these practices are missing.

Make someone responsible for the findings

Scanners produce findings. Someone has to read them. On a small team, the simplest setup is a rotating owner: one person per sprint who triages new alerts, closes the false positives and files tickets for the real ones. It spreads the knowledge around and stops alerts from piling up in a tab nobody opens.

It also helps to write down how you’ll respond when someone outside the team reports a bug. A security.txt file and a monitored inbox are enough to start. CISA’s Secure by Design guidance makes the same point: publish a vulnerability disclosure policy so people know where to send things.

Threat model the risky features

Tools are good at known bug patterns. They’re bad at logic flaws, like a refund endpoint that doesn’t check who placed the order. A short conversation before you build anything that touches money, auth or personal data catches those early. Ask what could go wrong, who would want to make it go wrong, and what would stop them. The OWASP Threat Modeling Cheat Sheet is a decent template if you want structure.

A realistic order of work

If you’re starting from nothing:

  • First: branch protection, MFA on the repo host and secret push protection.
  • Next: Dependabot or Renovate, plus one static analyzer in CI.
  • After that: pin your actions, trim workflow permissions and move CI to OIDC.
  • Then: a triage rotation and a disclosure policy.

None of this needs a security hire. It needs a few afternoons and someone willing to keep the alerts tidy.

Want help setting this up for your team? Contact us and we can walk through your pipeline together.

View all posts