DevSecOps automation: security gates in the pipeline

Security that requires a human to remember it does not scale. Security that is a pipeline gate cannot be forgotten. Here is the gate layout I use.

The gates

StageGateTooling
Merge requestSAST on diffSemgrep / vendor SAST
BuildDependency scanTrivy / grype
BuildImage scan (HIGH/CRITICAL fails)Trivy
PlanIaC securityCheckov / tfsec
StagingDASTZAP baseline

Each gate has a single behavior: fail the pipeline by default. Anything else — allowlists, exceptions, postponed fixes — requires a reviewed exception, not a silent pass.

What it looks like

# .gitlab-ci.yml — extract
stages: [sast, build, scan, deploy]

sast:
  stage: sast
  image: semgrep/semgrep
  script:
    - semgrep ci --config auto --sarif-output=semgrep.sarif

build:
  stage: build
  script:
    - docker build -t "$IMAGE_TAG" .

scan_image:
  stage: scan
  image: aquasec/trivy
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL "$IMAGE_TAG"

scan_iac:
  stage: scan
  script:
    - checkov -d infra --framework terraform --soft-fail-off

GitOps then promotes only what passed: ArgoCD deploys the tested tag, and the exception process — not the pipeline — is the special case.

The operational lessons

  1. Fail early, fail loudly. A scan that warns but never blocks becomes noise within a month. Gates must break the pipeline.
  2. Exceptions must expire. Every allowlist entry gets an expiry date and an owner. No permanent exceptions.
  3. Ownership stays with developers. The security team maintains the gates; teams fix what the gates find. Take fixing away from the author and it stops being fixed.
  4. Measure the boring numbers. Time-to-fix, false-positive rate, images shipped unscanned. If you can’t measure the gate, you’re not running it — you’re hoping.

The result

Vulnerabilities caught in minutes instead of months, and a deployment path where security is infrastructure: boring, repeatable, and hard to bypass.


Written by Rulx Philomé Alexis · ← All notes