In This Guide
- 1. Why "Shift Left" Isn't Enough — You Need "Shift Everywhere"
- 2. The DevSecOps Pipeline — Security at Every Stage
- 3. SAST — Finding Bugs in Source Code
- 4. SCA — Your Dependencies Are Your Attack Surface
- 5. Secrets Detection — Catching Leaked Credentials
- 6. Container and IaC Scanning
- 7. DAST — Testing the Running Application
- 8. Complete GitHub Actions Security Pipeline
- 9. Making It Work — Culture, Not Just Tools
- 10. Frequently Asked Questions
The median time to fix a critical vulnerability is 60 days (Veracode, 2024). Not because the fix is hard — usually it's a dependency bump or a one-line code change. It's 60 days because the vulnerability was found by a quarterly pen test, filed as a ticket, prioritized against feature work, assigned to someone who'd forgotten the context, and eventually deployed. DevSecOps exists to compress that 60-day feedback loop to 60 seconds.
We've implemented DevSecOps pipelines at Pillai Infotech for projects ranging from single-service APIs to multi-repo microservice architectures. This guide covers the tools, configurations, and — critically — the cultural changes that make it work.
1. Why "Shift Left" Isn't Enough — You Need "Shift Everywhere"
"Shift left" means finding security issues earlier in the development cycle. Good idea. But the slogan implies security is a phase that moves — it's not. Security needs to exist at every stage simultaneously.
| Stage | Security Activity | Tools | Feedback Time |
|---|---|---|---|
| IDE / Pre-commit | Secrets detection, basic SAST | gitleaks, Semgrep IDE extension | Instant (before commit) |
| Pull Request | Full SAST, SCA, license check | Semgrep, Snyk, Trivy | 2-5 minutes |
| Build | Container scan, IaC scan, SBOM | Trivy, Checkov, Syft | 3-8 minutes |
| Staging | DAST, API fuzzing | OWASP ZAP, Nuclei | 10-30 minutes |
| Production | Runtime protection, WAF, monitoring | Falco, Datadog ASM, Cloudflare WAF | Real-time |
| Ongoing | New CVE alerts, pen testing | GitHub Dependabot, Snyk Monitor | Continuous |
2. The DevSecOps Pipeline — Security at Every Stage
The key principle: fast checks run first, slow checks run later. Secrets detection in pre-commit takes milliseconds. DAST against a staging environment takes minutes. If you put the slow checks first, developers will disable them.
3. SAST — Finding Bugs in Source Code
Static Application Security Testing analyzes source code without running it. The modern tool of choice is Semgrep — it's fast, has great rules, and the open-source version covers most use cases.
| Tool | Languages | Speed | False Positive Rate | Cost |
|---|---|---|---|---|
| Semgrep | 30+ (JS, Python, Go, Java, PHP...) | Fast (seconds) | Low | Free OSS / paid Pro |
| CodeQL | C/C++, Java, JS, Python, Go, Ruby | Slow (minutes) | Very low | Free on GitHub |
| SonarQube | 29 languages | Medium | Medium-high | Free Community / paid |
| Bandit (Python) | Python only | Fast | Low-medium | Free |
- name: Run Semgrep SAST
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/nodejs
p/php
generateSarif: true
# Or run directly for more control:
- name: Semgrep scan
run: |
pip install semgrep
semgrep scan \
--config p/security-audit \
--config p/owasp-top-ten \
--error \ # Fail CI on findings
--severity ERROR \ # Only block on critical
--sarif -o semgrep.sarif \ # Upload to GitHub Security tab
.
Custom Semgrep Rules
The real power of Semgrep is writing rules specific to your codebase. Here's one we use:
rules:
- id: php-raw-sql-query
patterns:
- pattern: $DB->query("..." . $VAR . "...")
message: >
Direct variable interpolation in SQL query detected.
Use prepared statements: $db->execute("... WHERE id = ?", [$var])
languages: [php]
severity: ERROR
metadata:
cwe: CWE-89
owasp: A03:2021
- id: no-auth-middleware
patterns:
- pattern: |
$app->$METHOD($PATH, function($req, $res) { ... });
- pattern-not: |
$app->$METHOD($PATH, authenticate, ...);
message: Route handler without authentication middleware
languages: [javascript]
severity: WARNING
4. SCA — Your Dependencies Are Your Attack Surface
The average Node.js project pulls in 1,200+ transitive dependencies. You wrote 5,000 lines of code, but you're shipping 500,000 lines of other people's code. Software Composition Analysis (SCA) tracks those dependencies for known vulnerabilities.
# Node.js — built-in audit
npm audit --audit-level=high
# Fix automatically where possible:
npm audit fix
# Python
pip-audit --require-hashes --strict
# Or:
safety check --full-report
# Go
govulncheck ./...
# PHP (Composer)
composer audit
# Universal — Trivy scans any project type
trivy fs --severity HIGH,CRITICAL --exit-code 1 .
# Generate SBOM (Software Bill of Materials)
syft . -o spdx-json > sbom.spdx.json
Dependency Policy That Works
Not every vulnerability needs immediate action. Set a policy:
| Severity | CI Behavior | SLA | Example |
|---|---|---|---|
| Critical (CVSS 9+) | Block merge | Fix within 24 hours | Remote code execution in Express |
| High (CVSS 7-8.9) | Block merge | Fix within 7 days | SQL injection in ORM library |
| Medium (CVSS 4-6.9) | Warn, don't block | Fix within 30 days | ReDoS in validation library |
| Low (CVSS 0-3.9) | Log only | Fix in next dependency update cycle | Info disclosure in debug mode |
We started with "block on all vulnerabilities" and reverted within a week — developers couldn't merge because of low-severity findings in transitive dependencies they didn't control. The policy above took three iterations to get right. The key insight: block on things developers can actually fix quickly (direct dependencies, critical severity), warn on everything else.
5. Secrets Detection — Catching Leaked Credentials
GitHub scans public repos and finds over 1 million leaked secrets per year. Private repos leak too — they just take longer to discover. The fix is catching secrets before they enter version control.
# Install gitleaks
brew install gitleaks # macOS
# Or: go install github.com/gitleaks/gitleaks/v8@latest
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
# Run manually against entire repo history
gitleaks detect --source . --verbose
# Scan only staged changes (fast, for pre-commit)
gitleaks protect --staged --verbose
[extend]
useDefault = true # Include all default patterns
# Add custom patterns for your APIs
[[rules]]
id = "pillai-api-key"
description = "Pillai Infotech API Key"
regex = '''nc_bot_[a-zA-Z0-9]{10,}'''
secretGroup = 0
entropy = 3.5
[[rules]]
id = "openrouter-key"
description = "OpenRouter API Key"
regex = '''sk-or-v1-[a-f0-9]{64}'''
# Allowlist — files that legitimately contain example keys
[allowlist]
paths = [
'''\.env\.example''',
'''docs/api-examples\.md''',
]
If a secret is already committed: Rotating the credential is more important than removing it from git history. Even if you rewrite history with git filter-branch or BFG Repo Cleaner, the secret was likely already in caches, CI logs, or someone's local clone. Rotate first, clean history second. See our cybersecurity best practices guide for secrets management tools.
6. Container and IaC Scanning
Your Dockerfile and Terraform files are code. They can contain vulnerabilities, misconfigurations, and secrets just like application code.
Container Image Scanning with Trivy
# Scan a Docker image for OS and library vulnerabilities
trivy image --severity HIGH,CRITICAL myapp:latest
# Scan Dockerfile for misconfigurations
trivy config --severity HIGH,CRITICAL Dockerfile
# Scan Terraform files
trivy config --severity HIGH,CRITICAL ./terraform/
# Common Dockerfile issues Trivy catches:
# - Running as root (no USER instruction)
# - Using :latest tag (unpinned base image)
# - Secrets in ENV or ARG instructions
# - Package manager cache not cleaned
# - Unnecessary packages installed
Secure Dockerfile Pattern
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production # Deterministic install
COPY . .
RUN npm run build
# Stage 2: Production — minimal image
FROM node:20-alpine AS production
# ✅ Don't run as root
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
# ✅ Only copy what's needed
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
# ✅ Read-only filesystem where possible
USER appuser
# ✅ Health check for orchestrator
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "dist/server.js"]
IaC Scanning with Checkov
# Scan Terraform
checkov -d ./terraform/ --framework terraform
# Common findings:
# ✗ S3 bucket without encryption
# ✗ Security group with 0.0.0.0/0 ingress on port 22
# ✗ RDS without encryption at rest
# ✗ CloudTrail not enabled
# ✗ IAM policy with * resource
# Scan Kubernetes manifests
checkov -d ./k8s/ --framework kubernetes
# Common findings:
# ✗ Container running as root
# ✗ No resource limits set
# ✗ No readOnlyRootFilesystem
# ✗ hostNetwork: true
7. DAST — Testing the Running Application
DAST finds vulnerabilities that SAST can't — it tests the running application the way an attacker would. OWASP ZAP is the standard open-source DAST tool.
# Baseline scan — fast, finds low-hanging fruit (5-10 min)
docker run --rm -v $(pwd):/zap/wrk \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py \
-t https://staging.pillaiinfotech.com \
-r zap-report.html \
-l WARN
# Full scan — thorough, runs active attacks (30-60 min)
docker run --rm -v $(pwd):/zap/wrk \
ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py \
-t https://staging.pillaiinfotech.com \
-r zap-full-report.html
# API scan — import OpenAPI spec, test every endpoint
docker run --rm -v $(pwd):/zap/wrk \
ghcr.io/zaproxy/zaproxy:stable \
zap-api-scan.py \
-t https://staging.pillaiinfotech.com/openapi.json \
-f openapi \
-r zap-api-report.html
When to DAST: Run the baseline scan on every PR deploy to staging. Run the full scan weekly or before releases. API scans should run whenever the OpenAPI spec changes. Never run DAST against production — it generates real attack traffic. Use a staging environment that mirrors production. For dedicated penetration testing, hire professionals.
8. Complete GitHub Actions Security Pipeline
Here's a production-ready security pipeline that ties everything together. We use a variation of this across our projects at Pillai Infotech:
name: Security Pipeline
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for comprehensive scan
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit p/owasp-top-ten
generateSarif: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: HIGH,CRITICAL
exit-code: 1
format: sarif
output: trivy-results.sarif
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
container-scan:
runs-on: ubuntu-latest
needs: [sast, dependency-scan] # Only build if code is clean
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Trivy image scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: 1
iac-scan:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.changed_files, 'terraform')
steps:
- uses: actions/checkout@v4
- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
framework: terraform
soft_fail: false
Notice that secrets-scan, sast, and dependency-scan run in parallel — no need to wait. The container-scan only runs after the code checks pass (no point building an image if the code has vulnerabilities). Total pipeline time: 3-5 minutes for most projects.
9. Making It Work — Culture, Not Just Tools
We've seen teams with world-class tooling and terrible security outcomes. The tools only work if the culture supports them.
| Anti-Pattern | What Happens | Better Approach |
|---|---|---|
| Block on every finding | Developers add # nosemgrep everywhere |
Block on critical/high only, track rest as tech debt |
| Security team owns all findings | 3-month backlog, developers uninvolved | Developer who wrote the code fixes it, security team advises |
| Security review as gate | 2-week review queue, bypassed for "urgent" releases | Automated checks + security champions in each team |
| Only scan main branch | Vulnerabilities found after deploy | Scan on every PR — fix before merge |
| No triage process | 500 open findings, no one looks at them | Weekly triage: classify, prioritize, assign, SLA |
Security champions are the most effective cultural change we've seen. Pick one developer per team who:
- Reviews security findings for their team's services
- Attends a monthly security sync (not more — they're still developers)
- Writes custom Semgrep rules for common patterns in their codebase
- Is the first reviewer when a PR touches auth, crypto, or input handling
The metric that matters isn't "number of vulnerabilities found" — it's "mean time to remediate." We track MTTR by severity. Our target: critical findings fixed within 24 hours, high within a week. When MTTR starts creeping up, it's usually a culture problem (developers ignoring findings) not a tooling problem. The fix is always the same: make findings visible in the PR, not in a separate dashboard nobody checks.
10. Frequently Asked Questions
How much does DevSecOps slow down CI/CD?
With parallel execution, 2-5 minutes for SAST + SCA + secrets scanning. That's less than most test suites. DAST adds 10-30 minutes but runs on staging after merge, not blocking PRs. If your security pipeline takes longer than your test suite, something is misconfigured.
Should we use Snyk, GitHub Advanced Security, or open-source tools?
Start with open-source (Semgrep + Trivy + gitleaks). They cover 80% of use cases. Move to paid tools (Snyk, GitHub Advanced Security) when you need: fix suggestions with auto-PRs, reachability analysis (is the vulnerable code actually called?), compliance reporting, or SLA-backed vulnerability databases. Most teams under 50 developers do fine with open-source.
How do we handle false positives?
Suppress with inline comments (// nosemgrep: rule-id) and require a justification. Track suppressions — if one rule generates more suppressions than real findings, tune or disable it. Review suppressions quarterly. A tool with 50% false positive rate will be ignored within a month.
What if we're using a monorepo?
Scope scans to changed paths. Most CI tools support path filters — only run Node.js SCA when package.json changes, only run Terraform scanning when *.tf files change. Trivy and Semgrep both support --include and --exclude patterns. Full repo scans run on a nightly schedule, not on every PR.
Where should we start if we have no security scanning?
Day 1: Add gitleaks pre-commit hook (prevents the most embarrassing class of bugs). Day 2: Add npm audit / trivy fs to CI (finds known vulnerabilities in dependencies). Week 2: Add Semgrep with default rulesets. That's 80% of the value in under a week. Add DAST and container scanning as you mature. See our CI/CD pipeline guide for the full pipeline setup.
Pillai Infotech LLP
We integrate security into every pipeline we build — from CI/CD setup to cloud security hardening. Let's secure your pipeline.