The traditional model of security in software development goes like this: developers build a feature over several weeks, the security team reviews it before release, security finds problems, developers are unhappy about the last-minute rework, security is portrayed as “slowing things down,” and everyone resents everyone else.
DevSecOps is the recognition that this model is broken, not because security teams are incompetent, but because the feedback loop is too long. Security issues found in code review or penetration testing are expensive to fix. Security issues found at commit time are cheap. The entire DevSecOps philosophy is about moving security feedback as far left as possible.
Shifting Left in Practice
“Shift left” means finding issues earlier in the development lifecycle. The spectrum:
- Development: Pre-commit hooks, IDE plugins (cheap to fix)
- CI/CD: SAST, SCA, secret scanning, IaC scanning (cheap)
- Pre-production: DAST, container scanning, penetration testing (moderate)
- Production: Runtime security monitoring (expensive to fix)
Most organizations invest heavily in pre-production and production security while neglecting the development and CI/CD phases. This is backwards.
SAST: Static Application Security Testing
SAST analyzes your source code for security vulnerabilities without executing it. For Java, the options include:
Semgrep (my preference): Rule-based static analysis with a huge library of open-source security rules. Fast enough to run in CI, extensive Java rule sets, customizable.
# GitHub Actions
- name: SAST - Semgrep
uses: semgrep/semgrep-action@v1
with:
config: p/java
generateSarif: "1"
- name: Upload SARIF to GitHub
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
SpotBugs with FindSecBugs: Bytecode analysis that catches security issues that source-level tools miss. Runs as a Maven or Gradle plugin.
<!-- Maven -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<configuration>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>LATEST</version>
</plugin>
</plugins>
</configuration>
</plugin>
Run SAST on every PR. Gate the merge if critical issues are found. Don’t gate on every warning—tune your rules to reduce noise first, then tighten the gate.
SCA: Software Composition Analysis
Your application is mostly third-party dependencies. Those dependencies have CVEs. SCA tools track which dependencies you’re using and which CVEs affect them.
OWASP Dependency-Check: Free, integrates with Maven/Gradle/npm, outputs HTML and XML reports.
# Gradle
plugins {
id 'org.owasp.dependencycheck' version '9.0.9'
}
dependencyCheck {
failBuildOnCVSS = 7 // Fail on HIGH and CRITICAL
suppressionFile = 'dependency-check-suppressions.xml'
}
Trivy: Does SCA plus container image scanning. Run in CI to catch known CVEs:
# Scan a container image
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest
# Scan filesystem (source/dependencies)
trivy fs --exit-code 1 --severity HIGH,CRITICAL .
Snyk: Commercial, excellent developer experience, integrates with GitHub/GitLab for automatic PR comments when vulnerabilities are found.
The key practice: have a suppression/exception process for false positives and unfixable issues, but audit that list regularly. Suppressions that were “temporary” two years ago are still in the file.
Secret Scanning
Secrets in code are a persistent, underestimated problem. Developers accidentally commit API keys, database passwords, and certificates. These get pushed to GitHub, discovered by automated scanners, and you have an incident.
GitHub Secret Scanning: Built-in, free for public repos, catches common secret patterns (AWS keys, GCP service accounts, Stripe keys, etc.).
Gitleaks: Scan your repo’s entire history for secrets:
# Scan full git history
gitleaks detect --source . --report-format json --report-path gitleaks-report.json
# Pre-commit hook
gitleaks protect --staged
# .gitleaks.toml - customize rules
[allowlist]
description = "Global allowlist"
paths = [
".gitleaks.toml",
"test-fixtures/fake-secret.txt"
]
Install Gitleaks as a pre-commit hook so developers never commit secrets in the first place:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
Infrastructure as Code Scanning
Your Terraform, Kubernetes manifests, Helm charts, and Dockerfiles all have security implications. Scan them:
Checkov (Bridgecrew/Palo Alto): IaC security scanner supporting Terraform, CloudFormation, Kubernetes, Helm, Dockerfiles, and more.
# Scan Helm charts
checkov -d ./my-chart --framework helm
# Scan Kubernetes manifests
checkov -d ./clusters --framework kubernetes
# Scan Terraform
checkov -d ./terraform --framework terraform
Hadolint specifically for Dockerfiles:
hadolint Dockerfile
Common Dockerfile issues Hadolint catches:
apt-get installwithout pinned versions- Running as root
- Not using
--no-install-recommendson apt installs - Copying
.gitinto the image - Missing
HEALTHCHECK
Container Image Scanning
Container images accumulate CVEs over time. Even if you started with a clean image, the base OS packages age. Scan images both in CI (on build) and in the registry (continuously):
# Trivy in CI
- name: Scan container image
run: |
trivy image \
--exit-code 1 \
--ignore-unfixed \
--severity CRITICAL \
--format sarif \
--output trivy-results.sarif \
$
For registry-wide scanning, Trivy operator can run in your Kubernetes cluster and continuously scan all running images, reporting findings as Kubernetes CRDs you can query with kubectl.
Policy as Code
For Kubernetes, policy enforcement at admission time prevents misconfiguration from reaching the cluster. Options:
Kyverno: Kubernetes-native policy engine. Rules are Kubernetes YAML.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: enforce
rules:
- name: require-image-tag
match:
resources:
kinds:
- Pod
validate:
message: "Using ':latest' image tag is not allowed."
pattern:
spec:
containers:
- image: "!*:latest"
OPA/Gatekeeper: More flexible, uses Rego policy language. Steeper learning curve, more powerful.
The Security Champion Model
DevSecOps at the organizational level requires embedding security knowledge in development teams, not keeping it centralized in a security silo. Security champions—developers with security interest and training who serve as the first line of security review within their team—bridge the gap.
The tools and pipeline are necessary but not sufficient. If developers don’t understand why Semgrep flagged their code, they’ll add suppressions rather than fix the issue. Security education, embedded in the team, makes the tooling meaningful.
Building the Security Pipeline
Minimal viable security pipeline:
security:
stage: security
parallel:
matrix:
- SCAN_TYPE: sast
COMMAND: semgrep --config p/java .
- SCAN_TYPE: secrets
COMMAND: gitleaks detect --no-git .
- SCAN_TYPE: sca
COMMAND: trivy fs --severity HIGH,CRITICAL .
- SCAN_TYPE: iac
COMMAND: checkov -d . --framework kubernetes,helm
script:
- $COMMAND
Run all scans in parallel. Gate the pipeline on critical findings. Review and tune the rules regularly. This is the baseline from which you build a mature DevSecOps practice.
Security baked into the pipeline isn’t a tax on development velocity—it’s the thing that makes velocity sustainable without accumulating security debt that will eventually have to be paid.