CI/CD pipeline security
Your CI/CD pipeline is the last line of defense before code reaches production. Every commit, every pull request, every deployment passes through it. That makes it the perfect place to catch security issues — vulnerabilities in your code, dangerous dependencies, misconfigurations, leaked secrets.
The goal isn't to block every build with security warnings. It's to catch the critical issues automatically, give developers fast feedback, and stop obvious problems from shipping. A well-configured pipeline finds SQL injection before code review, flags vulnerable dependencies before merge, and blocks known-bad patterns before they reach production.
This chapter covers the five types of security scanning you need in CI/CD: static analysis (SAST), dependency scanning (SCA), software bill of materials (SBOM), infrastructure as code (IaC) scanning, and dynamic testing (DAST). All using free tools that work with GitHub and GitLab.
Why this matters for small companies
Security scanning sounds like enterprise overhead. It's not. For small teams, automated scanning is even more valuable — you don't have dedicated security reviewers, so the pipeline has to do that work.
You can't review everything manually. A team of five developers might produce dozens of commits per day. Nobody has time to security-review every change. Automated scanners catch the obvious issues so humans can focus on architecture and logic.
Vulnerabilities ship faster in small teams. No change advisory boards, no multi-stage approvals, no waiting. Code goes from laptop to production in hours. That speed is an advantage, but it means vulnerabilities move fast too. Scanning in CI/CD is your safety net.
Dependency risk is real. Your application might be 10,000 lines of code you wrote and 500,000 lines of open-source dependencies you didn't. One vulnerable package in that stack can compromise everything. Snyk's 2024 report found that 80% of applications contain at least one known vulnerability in their dependencies.
Free tools are production-ready. Semgrep, Trivy, Dependabot, npm audit — these aren't toy projects. They're used by thousands of companies and catch real vulnerabilities. You don't need a $50,000 security platform to scan your code.
What attackers look for
When attackers target an application, they often start with the easy wins:
- Known CVEs in dependencies — public exploits exist, just need to find a target using the vulnerable version
- Exposed secrets — API keys, database passwords, cloud credentials in code or CI logs
- Injection vulnerabilities — SQL injection, command injection, XSS that automated tools detect reliably
- Misconfigured infrastructure — open S3 buckets, permissive CORS, missing security headers
Automated scanning catches most of these. The attackers use automated tools to find them — you should use automated tools to find them first.
Types of security scanning
Five categories cover most of what you need:
| Type | What it does | When it runs | Examples |
|---|---|---|---|
| SAST (Static Application Security Testing) | Analyzes source code for vulnerabilities | Every commit, PR | Semgrep, CodeQL, Bandit |
| SCA (Software Composition Analysis) | Finds vulnerable dependencies | Every build | Dependabot, Snyk, Trivy |
| SBOM (Software Bill of Materials) | Inventories all components, tracks licenses | Every release | Syft, Grype, Dependency-Track |
| IaC Scanning | Finds misconfigurations in infrastructure code | Every commit | Checkov, tfsec, KICS |
| DAST (Dynamic Application Security Testing) | Tests running application for vulnerabilities | Staging deploys | OWASP ZAP, Nuclei |
Each catches different problems:
- SAST finds issues in code you write: SQL injection, XSS, insecure crypto
- SCA finds issues in code you import: vulnerable libraries, outdated packages
- SBOM tracks what's in your software: component inventory, license compliance, vulnerability correlation
- IaC finds issues in infrastructure: open S3 buckets, overly permissive IAM, unencrypted storage
- DAST finds issues in how it all runs: misconfigured servers, exposed endpoints, authentication bypasses
You need all five. They're complementary, not alternatives.
SAST: static code analysis
SAST tools read your source code and look for patterns that indicate vulnerabilities. No runtime, no deployment — just code analysis.
What SAST catches
| Vulnerability | How SAST finds it |
|---|---|
| SQL injection | String concatenation in SQL queries |
| XSS | Unsanitized output in HTML templates |
| Command injection | User input in shell commands |
| Path traversal | User input in file operations |
| Hardcoded secrets | Patterns matching API keys, passwords |
| Insecure crypto | Use of MD5, SHA1 for passwords, weak random |
Tool: Semgrep
Semgrep is the go-to SAST tool for small teams. It's fast, has good rules out of the box, and integrates with everything.
Install locally:
# macOS
brew install semgrep
# pip
pip install semgrep
Run a scan:
# Scan with default rules
semgrep --config auto .
# Scan with OWASP Top 10 rules
semgrep --config "p/owasp-top-ten" .
# Scan specific language
semgrep --config "p/python" .
GitHub Actions integration:
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
GitLab CI integration:
semgrep:
stage: test
image: semgrep/semgrep
script:
- semgrep ci --config auto
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Tool: CodeQL (GitHub only)
GitHub's CodeQL is powerful but only works on GitHub. It's free for public repos and included in GitHub Advanced Security for private repos.
Enable in repository settings:
- Go to Settings → Security → Code security and analysis
- Enable "Code scanning"
- Choose "CodeQL analysis"
- Select languages to scan
Or configure manually:
name: CodeQL
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Weekly Monday 6 AM
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
matrix:
language: ['javascript', 'python']
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
Language-specific tools
| Language | Tool | Install | Run |
|---|---|---|---|
| Python | Bandit | pip install bandit | bandit -r src/ |
| JavaScript | ESLint + security plugin | npm i eslint-plugin-security | eslint --ext .js src/ |
| Go | gosec | go install github.com/securego/gosec/v2/cmd/gosec@latest | gosec ./... |
| Ruby | Brakeman | gem install brakeman | brakeman |
| PHP | Psalm | composer require --dev vimeo/psalm | ./vendor/bin/psalm |
Handling SAST findings
Not every finding is a real vulnerability. SAST tools have false positives. Here's how to handle them:
Triage by severity:
| Severity | Action | Timeline |
|---|---|---|
| Critical/High | Block merge, fix immediately | Same day |
| Medium | Fix before release | This sprint |
| Low | Track, fix when convenient | Backlog |
Suppress false positives properly:
# Semgrep: inline ignore
password = get_from_vault() # nosemgrep: hardcoded-password
# Bandit: inline ignore
subprocess.run(cmd, shell=True) # nosec B602
Or use a configuration file:
# .semgrep.yml
rules:
- id: my-custom-rule
# ... rule definition
# Ignore specific paths
exclude:
- "tests/*"
- "vendor/*"
- "*.test.js"
Don't suppress everything. If you're suppressing more than 10% of findings, either your code has problems or you're using the wrong ruleset.
SCA: dependency scanning
Your dependencies are an attack surface. SCA tools scan your package manifests (package.json, requirements.txt, go.mod) and flag packages with known vulnerabilities.
What SCA catches
- Known CVEs — published vulnerabilities in specific package versions
- License issues — GPL dependencies in proprietary code, license conflicts
- Outdated packages — old versions missing security fixes
- Malicious packages — typosquatting, compromised maintainers
Tool: Dependabot (GitHub)
Dependabot is built into GitHub. It scans dependencies, opens PRs to update vulnerable packages, and keeps everything current.
Enable in repository:
- Go to Settings → Security → Code security and analysis
- Enable "Dependabot alerts"
- Enable "Dependabot security updates"
Or configure with a file:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
Group updates to reduce PR noise:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
development-dependencies:
dependency-type: "development"
production-dependencies:
dependency-type: "production"
update-types:
- "minor"
- "patch"
Tool: Snyk
Snyk works with GitHub, GitLab, Bitbucket, and standalone. Free tier covers unlimited tests for open source projects and limited scans for private repos.
Install CLI:
npm install -g snyk
snyk auth
Scan locally:
# Test current project
snyk test
# Test and show remediation
snyk test --show-vulnerable-paths=all
# Monitor for new vulnerabilities
snyk monitor
GitHub Actions integration:
name: Snyk Security
on:
push:
branches: [main]
pull_request:
jobs:
snyk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
GitLab CI integration:
snyk:
stage: test
image: snyk/snyk:node
script:
- snyk auth $SNYK_TOKEN
- snyk test --severity-threshold=high
allow_failure: true # Don't block on medium/low
Tool: Trivy
Trivy scans everything — container images, filesystems, git repos, Kubernetes manifests, Terraform. It's incredibly versatile.
Install:
# macOS
brew install trivy
# Linux
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
Scan filesystem (dependencies):
trivy fs --severity HIGH,CRITICAL .
Scan container image:
trivy image --severity HIGH,CRITICAL myapp:latest
GitHub Actions for container scanning:
name: Container Security
on:
push:
branches: [main]
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1'
Native package manager audits
Most package managers have built-in vulnerability scanning:
# npm
npm audit
npm audit fix
# yarn
yarn audit
# pip
pip-audit
# composer
composer audit
# bundler
bundle audit
# go
govulncheck ./...
Add to CI pipeline:
# GitHub Actions - npm audit
- name: Security audit
run: npm audit --audit-level=high
Handling dependency vulnerabilities
When a vulnerability has a fix:
- Update to the patched version
- Run tests
- Deploy
When there's no fix available:
- Check if you actually use the vulnerable function
- Assess real risk — is the vulnerable code path reachable?
- Consider alternative packages
- If acceptable risk, document and monitor
When updates break things:
- Check if it's a breaking change you can adapt to
- Pin to current version temporarily
- Create a ticket to properly fix
- Set a deadline — don't leave it forever
SBOM: Software Bill of Materials
An SBOM is a complete inventory of all components in your software — every library, framework, and dependency, with versions and origins. Think of it as an ingredients list for your application.
Why SBOM matters
Incident response speed. When Log4Shell hit in December 2021, organizations with SBOMs knew within hours which applications were affected. Those without spent days or weeks manually investigating. The U.S. Cybersecurity Executive Order 14028 now requires SBOMs for software sold to the federal government.
Supply chain visibility. Your app depends on package A, which depends on B, which depends on C. A vulnerability in C affects you, but you might not know C exists without an SBOM. Transitive dependencies are invisible without proper tooling.
License compliance. That MIT-licensed package you're using might depend on a GPL library. Without an SBOM, you won't know until legal comes knocking. License analysis requires knowing every component in your stack.
Customer requirements. Enterprise customers increasingly require SBOMs as part of vendor security assessments. Healthcare, finance, and government sectors often mandate them.
SBOM formats
Two main formats dominate:
| Format | Maintainer | Best for | Link |
|---|---|---|---|
| SPDX | Linux Foundation | License compliance, broad adoption | spdx.dev |
| CycloneDX | OWASP | Security focus, vulnerability tracking | cyclonedx.org |
Both are machine-readable (JSON, XML) and interchangeable for most purposes. CycloneDX has better vulnerability correlation; SPDX has broader industry adoption for license compliance.
Generating SBOMs
Tool: Syft — the standard for SBOM generation
# Install
brew install syft
# or
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate SBOM from directory
syft dir:. -o cyclonedx-json > sbom.json
# Generate from container image
syft myapp:latest -o spdx-json > sbom.spdx.json
# Generate from package lock files
syft dir:. -o cyclonedx-json --catalogers javascript
GitHub Actions:
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
GitLab CI:
generate_sbom:
stage: build
image: anchore/syft
script:
- syft dir:. -o cyclonedx-json > sbom.json
artifacts:
paths:
- sbom.json
Other SBOM generators:
| Tool | Type | Best for | Link |
|---|---|---|---|
| Syft | CLI | Universal generation | GitHub |
| Trivy | CLI | Combined scanning + SBOM | trivy.dev |
| cdxgen | CLI | CycloneDX native | GitHub |
| Microsoft SBOM Tool | CLI | .NET, general | GitHub |
| SPDX SBOM Generator | CLI | SPDX native | GitHub |
Analyzing SBOMs for vulnerabilities
Once you have an SBOM, scan it for known vulnerabilities:
Tool: Grype — vulnerability scanner that consumes SBOMs
# Install
brew install grype
# Scan SBOM for vulnerabilities
grype sbom:sbom.json
# Scan with severity filter
grype sbom:sbom.json --only-fixed --fail-on high
GitHub Actions:
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:latest
output-file: sbom.json
- name: Scan SBOM for vulnerabilities
uses: anchore/scan-action@v3
with:
sbom: sbom.json
fail-build: true
severity-cutoff: high
Other vulnerability scanners for SBOMs:
| Tool | Price | Features | Link |
|---|---|---|---|
| Grype | Free | Fast, SBOM-native | GitHub |
| Trivy | Free | Universal scanner | trivy.dev |
| OSV-Scanner | Free | Google's OSV database | GitHub |
| Snyk | Free tier | Best remediation advice | snyk.io |
| OWASP Dependency-Track | Free (self-hosted) | Full SBOM management platform | dependencytrack.org |
| Anchore Enterprise | Paid | Policy engine, compliance | anchore.com |
| Sonatype Nexus Lifecycle | Paid | Deep analysis, policies | sonatype.com |
Analyzing SBOMs for license compliance
License issues can be as damaging as security vulnerabilities — legal exposure, forced open-sourcing of proprietary code, or inability to ship to certain customers.
Common license concerns:
| License type | Concern | Example licenses |
|---|---|---|
| Copyleft | May require open-sourcing your code | GPL, AGPL, LGPL |
| Weak copyleft | Requires attribution, some restrictions | MPL, EPL |
| Permissive | Generally safe for commercial use | MIT, Apache 2.0, BSD |
| Unknown | No license = all rights reserved | Unlicensed packages |
Tool: OWASP Dependency-Track — complete SBOM management
Dependency-Track is a free, self-hosted platform that ingests SBOMs and provides:
- Vulnerability tracking across all projects
- License compliance analysis
- Policy enforcement
- Historical tracking
# Run with Docker
docker run -d -p 8080:8080 dependencytrack/bundled
Then upload SBOMs via API or UI.
Commercial license compliance tools:
| Tool | Price | Features | Link |
|---|---|---|---|
| OWASP Dependency-Track | Free (self-hosted) | Full SBOM platform | dependencytrack.org |
| FOSSA | Free tier, paid plans | Deep license analysis | fossa.com |
| Snyk | Free tier | License scanning included | snyk.io |
| Mend (WhiteSource) | Paid | Enterprise compliance | mend.io |
| Black Duck | Enterprise | Deep license intelligence | synopsys.com |
| Sonatype Nexus | Paid | License policies | sonatype.com |
| Snyk | Free tier | Unified security + license | snyk.io |
Quick license scan with Trivy:
# Scan for licenses
trivy fs --scanners license .
# Check for specific problematic licenses
trivy fs --scanners license --license-full .
GitHub Actions for license compliance:
- name: License check
uses: fossas/fossa-action@main
with:
api-key: ${{ secrets.FOSSA_API_KEY }}
SBOM in CI/CD workflow
Integrate SBOM generation and analysis into your pipeline:
# Complete SBOM workflow
name: SBOM Pipeline
on:
push:
branches: [main]
release:
types: [published]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
# Generate SBOM
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.json
# Scan for vulnerabilities
- name: Vulnerability scan
uses: anchore/scan-action@v3
with:
sbom: sbom.json
fail-build: true
severity-cutoff: high
# Check licenses
- name: License scan
run: |
trivy fs --scanners license --severity HIGH,CRITICAL . \
--exit-code 1
# Store SBOM with release
- name: Upload SBOM to release
if: github.event_name == 'release'
uses: softprops/action-gh-release@v1
with:
files: sbom.json
SBOM best practices
-
Generate with every build. SBOMs should be artifacts, versioned alongside your releases.
-
Store SBOMs with releases. Attach to GitHub releases, store in artifact registry, or push to Dependency-Track.
-
Automate vulnerability scanning. Don't just generate — analyze. Set up alerts for new CVEs affecting your components.
-
Define license policies. Decide which licenses are acceptable for your use case. Block builds that introduce problematic licenses.
-
Share with customers. If customers require SBOMs, automate delivery. Don't make it a manual process.
-
Monitor continuously. New vulnerabilities are discovered daily. Scan stored SBOMs against updated vulnerability databases.
DAST: dynamic testing
DAST tools test running applications by sending requests and analyzing responses. They find issues that static analysis can't see — runtime misconfigurations, authentication bypasses, response header problems.
What DAST catches
| Issue | How DAST finds it |
|---|---|
| Missing security headers | Checks response headers |
| XSS | Injects payloads, checks for reflection |
| SQL injection | Injects SQL, checks for errors/behavior changes |
| Authentication bypass | Tests access without valid credentials |
| CORS misconfiguration | Tests cross-origin requests |
| SSL/TLS issues | Tests certificate, protocol versions |
Tool: OWASP ZAP
ZAP (Zed Attack Proxy) is the standard open-source DAST tool. It can run automated scans or be used as an intercepting proxy for manual testing.
Docker-based CI scan:
# GitHub Actions
name: DAST Scan
on:
push:
branches: [main]
jobs:
zap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start application
run: |
docker compose up -d
sleep 30 # Wait for app to start
- name: ZAP Baseline Scan
uses: zaproxy/action-[email protected]
with:
target: 'http://localhost:3000'
rules_file_name: '.zap/rules.tsv'
allow_issue_writing: false
GitLab CI integration:
dast:
stage: test
image: ghcr.io/zaproxy/zaproxy:stable
script:
- mkdir -p /zap/wrk
- zap-baseline.py -t $STAGING_URL -r report.html -I
artifacts:
paths:
- report.html
only:
- main
Baseline vs Full scan:
| Scan type | Duration | Coverage | Use case |
|---|---|---|---|
| Baseline | 1-5 minutes | Passive checks, no attacks | Every deploy |
| Full | 30-60+ minutes | Active attacks, thorough | Weekly, pre-release |
Tool: Nuclei
Nuclei is fast, template-based, and great for specific vulnerability checks.
Install:
# macOS/Linux
brew install nuclei
# or
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
Run scans:
# Default templates
nuclei -u https://staging.example.com
# Specific severity
nuclei -u https://staging.example.com -severity high,critical
# Specific templates
nuclei -u https://staging.example.com -t cves/ -t misconfigurations/
CI integration:
nuclei:
stage: test
image: projectdiscovery/nuclei
script:
- nuclei -u $STAGING_URL -severity high,critical -o results.txt
artifacts:
paths:
- results.txt
When to run DAST
DAST needs a running application, which complicates CI/CD integration:
Option 1: Test staging after deploy
deploy_staging:
stage: deploy
script:
- ./deploy-staging.sh
dast_scan:
stage: test
needs: [deploy_staging]
script:
- zap-baseline.py -t $STAGING_URL
Option 2: Spin up ephemeral environment
dast:
stage: test
services:
- docker:dind
script:
- docker compose up -d
- sleep 30
- nuclei -u http://docker:3000 -severity critical
- docker compose down
Option 3: Scheduled scans (not in PR pipeline)
# Run weekly, not on every commit
dast_weekly:
stage: security
script:
- zap-full-scan.py -t $PRODUCTION_URL -r report.html
only:
- schedules
IaC scanning: infrastructure as code
If you use Terraform, CloudFormation, or Kubernetes manifests, you need IaC scanning. Misconfigurations in infrastructure code are just as dangerous as application vulnerabilities — open S3 buckets, overly permissive IAM roles, unencrypted databases.
What IaC scanning catches
| Issue | Example |
|---|---|
| Public S3 buckets | acl = "public-read" |
| Overly permissive IAM | Action: "*", Resource: "*" |
| Unencrypted storage | Missing encrypted = true |
| Open security groups | 0.0.0.0/0 ingress |
| Missing logging | CloudTrail, VPC flow logs disabled |
| Hardcoded secrets | API keys in Terraform variables |
Tool: Checkov
Checkov is the most comprehensive free IaC scanner. It supports Terraform, CloudFormation, Kubernetes, Helm, ARM, and more.
Install:
pip install checkov
Scan locally:
# Scan Terraform directory
checkov -d terraform/
# Scan specific file
checkov -f main.tf
# Output as JSON for CI
checkov -d terraform/ -o json
GitHub Actions:
- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
soft_fail: false
skip_check: CKV_AWS_18,CKV_AWS_21 # Skip specific checks if needed
GitLab CI:
checkov:
stage: test
image: bridgecrew/checkov
script:
- checkov -d terraform/ --output cli --output junitxml --output-file-path console,results.xml
artifacts:
reports:
junit: results.xml
Tool: tfsec (Terraform-specific)
If you only use Terraform, tfsec is faster and Terraform-focused.
Install:
brew install tfsec
Scan:
tfsec terraform/
tfsec terraform/ --severity-override=HIGH,CRITICAL
GitHub Actions:
- name: tfsec
uses: aquasecurity/tfsec-[email protected]
with:
working_directory: terraform/
Handling IaC findings
IaC findings often require context. A "public S3 bucket" might be intentional (hosting a static website) or a critical issue (storing customer data).
Suppress intentional configurations:
# tfsec:ignore:aws-s3-no-public-access
resource "aws_s3_bucket" "website" {
# This bucket intentionally hosts public website
bucket = "company-website-assets"
}
# Checkov skip
resource "aws_s3_bucket" "website" {
#checkov:skip=CKV_AWS_18:This bucket hosts public website
bucket = "company-website-assets"
}
Putting it together
A complete security scanning setup for a typical web application:
GitHub Actions example
name: Security Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# SAST - runs on every commit
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep
uses: semgrep/semgrep-action@v1
with:
config: p/security-audit p/secrets
# SCA - runs on every commit
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: npm audit
run: npm audit --audit-level=high
- name: Snyk test
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
continue-on-error: true # Alert but don't block
# Container scanning - if building images
container:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1'
# DAST - only on main branch after deploy
dast:
runs-on: ubuntu-latest
needs: [sast, sca]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: ./deploy-staging.sh
- name: ZAP Baseline
uses: zaproxy/action-[email protected]
with:
target: ${{ secrets.STAGING_URL }}
GitLab CI example
stages:
- test
- build
- security
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# SAST
semgrep:
stage: test
image: semgrep/semgrep
script:
- semgrep ci --config auto
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# SCA - dependencies
dependency_scan:
stage: test
image: node:20
script:
- npm ci
- npm audit --audit-level=high
allow_failure: true
# SCA - Snyk
snyk:
stage: test
image: snyk/snyk:node
script:
- snyk auth $SNYK_TOKEN
- snyk test --severity-threshold=high
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Build container
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
# Container scanning
trivy:
stage: security
image: aquasec/trivy
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $DOCKER_IMAGE
needs: [build]
# DAST on staging
dast:
stage: security
image: ghcr.io/zaproxy/zaproxy:stable
script:
- zap-baseline.py -t $STAGING_URL -I
needs: [deploy_staging]
only:
- main
When scanners find issues: Security Champion response playbook
Your pipeline blocked a merge request. Or worse — it flagged something in code that's already in production. What now?
This section provides a structured approach for Security Champions to handle security findings, from initial triage to resolution. The process follows modern vulnerability management methodologies including CVSS, SSVC, and VEX.
Step 1: initial triage (5-10 minutes)
Before diving deep, quickly assess what you're dealing with:
Gather basic information:
| Question | Where to find it |
|---|---|
| What scanner found it? | Pipeline output, tool name |
| What's the finding type? | CVE ID, CWE, rule ID |
| Where is it? | File, line number, dependency |
| What severity does the tool report? | Critical/High/Medium/Low |
| Is this new code or existing? | PR diff, git blame |
Quick classification:
Initial triage questions:
- Is this a known false positive? → Yes: document and suppress (Step 6). No: continue to Step 2.
- Is this in production right now? → Yes: parallel track — assess production exposure (Step 5). No: continue normal flow.
- Is this blocking a critical deployment? → Yes: escalate immediately, get more eyes. No: continue methodically.
Step 2: verify the finding (15-30 minutes)
Not every scanner alert is a real vulnerability. Verify before investing more time.
For SAST findings (code vulnerabilities):
- Read the flagged code. Understand what it does.
- Check the data flow. Does user input actually reach this code?
- Trace the input source. Is it validated/sanitized upstream?
- Check for existing protections. Framework security features, WAF rules, input validation.
# Example: Semgrep flags this as SQL injection
query = f"SELECT * FROM users WHERE id = {user_id}"
# Questions to answer:
# 1. Where does user_id come from? (request parameter? internal service?)
# 2. Is it validated before this line? (type check? allowlist?)
# 3. Is there a WAF or parameterization layer above?
For SCA findings (vulnerable dependencies):
- Check if you use the vulnerable function. Most CVEs affect specific features.
- Read the CVE description. What's the attack vector?
- Check your code. Do you call the vulnerable API?
- Check transitive exposure. Does another dependency use the vulnerable feature?
# Find where a dependency is used
grep -r "require('vulnerable-package')" src/
grep -r "from vulnerable_package import" src/
# Check if specific vulnerable function is called
grep -r "vulnerableFunction(" src/
For container/IaC findings:
- Check if the configuration is intentional. Some "insecure" configs are valid for your use case.
- Verify the actual risk. Is the resource exposed? Is there compensating control?
- Check runtime vs build. Some findings only matter in specific environments.
Verification outcome:
| Result | Action |
|---|---|
| Confirmed vulnerability | Continue to Step 3 |
| False positive | Document and suppress (Step 6) |
| Uncertain | Get a second opinion, default to treating as real |
Step 3: assess exploitability (15-45 minutes)
A confirmed vulnerability isn't necessarily exploitable in your context. Assess real-world risk.
Exploitability factors (based on CVSS v4.0):
| Factor | Questions to answer |
|---|---|
| Attack Vector | Network accessible? Local only? Physical access needed? |
| Attack Complexity | Easy to exploit? Requires specific conditions? |
| Privileges Required | Anonymous? Authenticated user? Admin? |
| User Interaction | None? Victim must click link? |
Context-specific assessment:
| Factor | Questions to answer |
|---|---|
| Exposure | Is this code/component reachable from the internet? |
| Data sensitivity | What data could be accessed if exploited? |
| Existing controls | WAF, rate limiting, authentication, network segmentation? |
| Exploit availability | Public exploit? Metasploit module? Theoretical only? |
Quick exploitability check:
# Check if a public exploit exists
searchsploit CVE-2024-XXXXX
# Check ExploitDB
curl -s "https://www.exploit-db.com/search?cve=2024-XXXXX"
# Check CISA KEV (Known Exploited Vulnerabilities)
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json | \
jq '.vulnerabilities[] | select(.cveID=="CVE-2024-XXXXX")'
Exploitability matrix:
| Exploit available | Externally reachable | Sensitive data at risk | Priority |
|---|---|---|---|
| Yes | Yes | Yes | CRITICAL — Immediate action |
| Yes | Yes | No | HIGH — Fix this sprint |
| Yes | No | Yes | HIGH — Fix this sprint |
| No | Yes | Yes | MEDIUM — Fix soon |
| No | No | No | LOW — Track, fix when convenient |
Step 4: determine business impact (10-20 minutes)
Technical severity ≠ business impact. A critical CVE in a dev tool matters less than a medium issue in your payment system.
Impact assessment questions:
| Category | Questions |
|---|---|
| Confidentiality | What data could leak? PII? Credentials? Business secrets? |
| Integrity | Could data be modified? Financial records? User permissions? |
| Availability | Could the system be crashed? For how long? |
| Compliance | Does this violate regulations? GDPR, PCI DSS, HIPAA? |
| Reputation | Would this make headlines if exploited? |
Calculate priority (SSVC-inspired):
Score each factor and sum the result:
- Exploitation — Active in the wild: +2 / PoC exists: +1 / None known: +0
- Exposure — Internet-facing unauthenticated: +2 / Authenticated: +1 / Internal only: +0
- Impact — Full system compromise: +2 / Data access or modification: +1 / Limited: +0
Total: 5–6 → Immediate | 3–4 → Urgent | 1–2 → Scheduled
Step 5: check production exposure (parallel track)
If the vulnerable code/component is in production, you need to know immediately.
Questions to answer:
-
Is the vulnerability in production?
- Check deployed versions
- Check release history
- When was it introduced?
-
Has it been exploited?
- Check application logs for suspicious patterns
- Check WAF logs for attack signatures
- Check access logs for anomalies
- Check SIEM alerts
-
What's the blast radius?
- Which environments are affected?
- Which customers/users?
- How long has it been exposed?
Production investigation checklist:
# Check if vulnerable version is deployed
kubectl get deployments -o jsonpath='{.items[*].spec.template.spec.containers[*].image}'
# Search logs for exploit patterns (example for SQL injection)
grep -E "(UNION|SELECT.*FROM|DROP TABLE|--|;)" /var/log/app/*.log
# Check for unusual error rates
grep "500\|502\|503" /var/log/nginx/access.log | wc -l
# Check authentication failures spike
grep "authentication failed" /var/log/app/*.log | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -20
If exploitation is suspected:
- Contain immediately — Disable affected feature, block IPs, rotate credentials
- Preserve evidence — Don't delete logs, take snapshots
- Engage incident response — This is now a security incident, not just a vulnerability
- Notify stakeholders — Leadership, legal, compliance as required
Step 6: decide and act
Based on your assessment, choose the appropriate response:
Response options:
| Priority | Response | Timeline | Who decides |
|---|---|---|---|
| Immediate | Block merge, hotfix production | Hours | Security Champion + Lead |
| Urgent | Fix in current sprint, production patch scheduled | Days | Security Champion |
| Scheduled | Add to backlog, fix in normal development | Weeks | Security Champion |
| Accept risk | Document decision, implement monitoring | N/A | Security Champion + Management |
| False positive | Suppress with documentation | Immediate | Security Champion |
For confirmed vulnerabilities — fix workflow:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Create │────►│ Implement │────►│ Verify │────►│ Deploy │
│ ticket │ │ fix │ │ fix │ │ + close │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
▼ ▼ ▼ ▼
- CVE/CWE ID - Code change - Run scanner - Deploy to prod
- Severity - Dependency - Manual verify - Monitor for issues
- Impact analysis update - Regression - Update SBOM
- Affected versions - Config fix tests - Close ticket
For false positives — suppression workflow:
# 1. Document WHY it's a false positive
# semgrep: .semgrepignore or inline
def process_data(data):
# nosemgrep: sql-injection
# Reason: 'data' is validated UUID from internal service, not user input
query = f"SELECT * FROM cache WHERE id = '{data}'"
# 2. Add to team knowledge base
# Create entry in security wiki explaining this pattern
# 3. Consider improving the rule
# Report to Semgrep community if rule has high false positive rate
For accepted risks:
## Risk Acceptance Record
**Vulnerability:** CVE-2024-XXXXX in package-name v1.2.3
**Date:** 2024-12-28
**Decided by:** [Name], approved by [Manager]
### Risk assessment
- Severity: HIGH (CVSS 8.1)
- Exploitability in our context: LOW
- Reason: Vulnerable function not used in our codebase
### Mitigating controls
- [ ] WAF rule blocks attack pattern
- [ ] Input validation in place
- [ ] Monitoring for exploitation attempts
### Review schedule
- Next review: 2025-03-28
- Trigger for re-review: New exploit published, or mitigating control removed
### Acceptance signature
Accepted by: _____________ Date: _____________
Step 7: prevent recurrence
After fixing, prevent the same issue from appearing again.
Actions:
- Add specific scanner rules — If custom code pattern, add Semgrep rule
- Update dependencies policy — Block vulnerable versions in lockfile
- Add to security training — Share as case study with team
- Improve detection — If caught late, improve scanning coverage
Example: prevent future use of vulnerable pattern:
# .semgrep.yml - custom rule to prevent pattern
rules:
- id: no-string-format-sql
patterns:
- pattern: f"SELECT ... {$VAR} ..."
message: "Use parameterized queries, not string formatting"
languages: [python]
severity: ERROR
Response time guidelines
| Priority | Initial response | Fix implemented | Production patched |
|---|---|---|---|
| Immediate (Active exploit, critical data) | 1 hour | 4 hours | 8 hours |
| Urgent (Public exploit, external exposure) | 4 hours | 24 hours | 48 hours |
| Scheduled (No exploit, limited exposure) | 24 hours | 1-2 weeks | Next release |
| Low (Theoretical, no exposure) | 1 week | 1-2 months | When convenient |
Escalation matrix
| Situation | Escalate to | Method |
|---|---|---|
| Active exploitation in progress | CTO, Incident Response, Legal | Phone call |
| Critical vulnerability in production | Engineering Lead, CTO | Slack + call |
| Unsure about severity/exploitability | Senior engineer, external consultant | Slack |
| Risk acceptance needed | Engineering Manager | Email with documentation |
| Vendor patch required | Vendor security contact | Email, support ticket |
Documentation template
Use this template for every significant finding:
## Security Finding Report
### Summary
- **Finding ID:** [JIRA/ticket number]
- **Scanner:** [Semgrep/Snyk/Trivy/etc]
- **Rule/CVE:** [Rule ID or CVE number]
- **Severity (tool):** [Critical/High/Medium/Low]
- **Severity (assessed):** [After your analysis]
- **Status:** [New/Investigating/Confirmed/Fixed/False Positive/Accepted]
### Technical details
- **Location:** [file:line or package:version]
- **Introduced:** [commit/date/PR]
- **In production:** [Yes/No, since when]
### Analysis
- **Verification result:** [Confirmed/False positive]
- **Exploitability:** [High/Medium/Low/None]
- **Attack vector:** [Description]
- **Existing controls:** [WAF/validation/etc]
### Impact
- **Confidentiality:** [None/Low/High]
- **Integrity:** [None/Low/High]
- **Availability:** [None/Low/High]
- **Business impact:** [Description]
### Resolution
- **Action taken:** [Fix/Accept/Suppress]
- **Fix PR:** [Link]
- **Deployed:** [Date]
- **Verified:** [How]
### Prevention
- **New scanner rule:** [Yes/No, link]
- **Training update:** [Yes/No]
- **Process change:** [Description]
Common mistakes
Blocking everything
# BAD: Every finding blocks the build
- run: npm audit # Fails on ANY vulnerability
This leads to ignored pipelines, disabled checks, or "fix later" culture.
# GOOD: Block on critical, warn on medium
- run: npm audit --audit-level=critical
Not blocking anything
# BAD: Scan runs but never fails
- run: semgrep --config auto || true
Security findings pile up, nobody looks at reports.
# GOOD: Block on high severity
- run: semgrep --config auto --error --severity=ERROR
Running DAST on production
# BAD: Attacking your own production
- run: zap-full-scan.py -t https://production.example.com
Full DAST scans can break things. Use staging.
# GOOD: Attack staging only
- run: zap-full-scan.py -t $STAGING_URL
Ignoring tool output
Setting up scanning without a process for handling findings. Tools find issues → nobody triages → alerts get ignored → tools get disabled.
Fix: Assign ownership. Someone reviews findings weekly. Critical issues create tickets. Medium issues get reviewed monthly.
Too many tools
Running five different SAST tools, three SCA scanners, and hoping more is better. Result: noise, duplicate findings, slow pipelines.
Fix: Start with one tool per category. Add more only if you have specific gaps.
Scanning only on main
# BAD: Issues found after merge
only:
- main
Find issues in PRs, not after they're merged.
# GOOD: Scan on every PR
on:
push:
branches: [main]
pull_request: # This is key
Real-world incidents
Codecov supply chain attack (2021). Attackers compromised Codecov's bash uploader script used in thousands of CI/CD pipelines. For over two months, the modified script exfiltrated environment variables — including CI secrets, API tokens, and credentials — from every pipeline that used it. Companies like Twilio, HashiCorp, and Confluent were affected.
ua-parser-js hijack (2021). A popular npm package (8 million weekly downloads) was hijacked when an attacker compromised the maintainer's npm account. Malicious versions mined cryptocurrency and stole passwords. Projects with SCA scanning detected the issue within hours; those without it shipped compromised builds.
Log4Shell (2021). CVE-2021-44228 in Log4j affected virtually every Java application. Organizations with SCA scanning knew their exposure within hours. Those without spent days figuring out which applications used Log4j and which versions. The difference between "we're patched" and "we're still investigating" was automated dependency scanning.
These incidents share a pattern: organizations with automated scanning detected and responded faster. Those without it scrambled.
Workshop: secure your pipeline
Block 2-3 hours for this exercise.
Part 1: add SAST scanning (30 minutes)
For GitHub:
- Create
.github/workflows/security.yml:
name: Security Scan
on:
push:
branches: [main]
pull_request:
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/security-audit p/secrets
- Commit and push
- Create a test PR and verify the scan runs
- Review any findings
For GitLab:
- Add to
.gitlab-ci.yml:
semgrep:
stage: test
image: semgrep/semgrep
script:
- semgrep ci --config auto
- Commit and push
- Verify scan runs on pipeline
- Review findings in pipeline output
Deliverable: Working SAST scan in CI
Part 2: configure Dependabot or Snyk (30 minutes)
Option A — Dependabot (GitHub):
- Create
.github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "npm" # or pip, bundler, etc.
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
- Go to Settings → Security → Enable Dependabot alerts and security updates
- Review any existing alerts in Security tab
Option B — Snyk:
- Sign up at snyk.io (free tier)
- Install CLI:
npm install -g snyk - Authenticate:
snyk auth - Run initial scan:
snyk test - Add to CI (see examples above)
Deliverable: Dependency scanning enabled with initial findings reviewed
Part 3: add container scanning (30 minutes)
If you use Docker:
- Add Trivy to your workflow:
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:test .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:test
severity: 'CRITICAL,HIGH'
- Build your image locally and scan:
docker build -t myapp:test .
trivy image myapp:test
- Review findings and fix critical issues
Deliverable: Container scanning in CI with base image vulnerabilities documented
Part 4: set up DAST baseline (30 minutes)
-
Deploy your app to a staging environment (or run locally)
-
Run ZAP baseline scan:
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t http://your-staging-url
-
Review the output — check for:
- Missing security headers
- Cookie security issues
- Information disclosure
-
Add baseline scan to CI pipeline (staging only)
Deliverable: DAST baseline scan configured for staging
Part 5: add IaC scanning (20 minutes)
If you use Terraform, CloudFormation, or Kubernetes:
- Install Checkov:
pip install checkov
- Scan your infrastructure code:
checkov -d terraform/ # or cloudformation/, k8s/
- Add to CI:
# GitHub Actions
- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
soft_fail: false
- Review findings and fix critical misconfigurations
Deliverable: IaC scanning in CI with major issues documented
Part 6: set up SBOM generation (20 minutes)
- Install Syft:
brew install syft
# or
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- Generate SBOM for your project:
syft dir:. -o cyclonedx-json > sbom.json
- Scan SBOM for vulnerabilities:
brew install grype
grype sbom:sbom.json
- Add to CI:
# GitHub Actions
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
path: .
format: cyclonedx-json
output-file: sbom.json
- name: Scan SBOM
uses: anchore/scan-action@v3
with:
sbom: sbom.json
fail-build: true
severity-cutoff: high
- Review license information in the SBOM
Deliverable: SBOM generation and scanning in CI
Part 7: create triage process (30 minutes)
Create a simple document:
# Security Findings Triage Process
## Severity levels
- **Critical/High**: Block merge, fix immediately
- **Medium**: Fix in current sprint
- **Low**: Add to backlog, fix when convenient
## Who triages
- [Name] reviews findings weekly
- Critical findings notify #security-alerts channel
## False positives
- Document in `.semgrep.yml` or tool config
- Require approval from [Name] to suppress
## Metrics tracked
- Open findings by severity
- Time from finding to fix
- Findings per deployment
Deliverable: Documented triage process
Artifacts from this chapter
By the end of this chapter, you should have:
- SAST scanner configured — Semgrep or CodeQL running on every PR
- Dependabot/Snyk enabled — Dependency scanning with alerts configured
- Container scanning — Trivy scanning Docker images (if applicable)
- IaC scanning — Checkov or tfsec scanning infrastructure code (if applicable)
- SBOM generation — Syft generating SBOMs with each build
- DAST baseline — ZAP or Nuclei running on staging deploys
- Security workflow file — Complete CI/CD security pipeline configuration
- Finding response playbook — Step-by-step process for handling scanner findings
- Escalation matrix — Who to contact for different severity levels
- Triage process document — How findings are prioritized and assigned
Talking to leadership
When someone asks why you're adding security scanning to CI/CD:
"I'm adding automated security checks to our deployment pipeline. These tools scan our code for vulnerabilities, check our dependencies for known security issues, and test the running application for common attack vectors. It catches problems before they reach production — SQL injection, vulnerable libraries, misconfigured servers. The tools are free and add maybe 2-3 minutes to our pipeline. The alternative is finding these issues after a breach, which is significantly more expensive in terms of both money and reputation."
Short version: "I'm adding automated security checks so we catch vulnerabilities before attackers do."
Self-check
Static analysis (SAST)
- Semgrep or CodeQL running on every PR
- High-severity findings block merge
- False positive suppression process documented
- Team knows how to read SAST findings
Dependency scanning (SCA)
- Dependabot or Snyk enabled
- Alerts configured and being reviewed
- Critical dependency vulnerabilities fixed
- Process for handling unfixable vulnerabilities
Container scanning
- Trivy scanning images before push to registry
- Base images updated to fix critical vulnerabilities
- Scanning integrated into build pipeline
Infrastructure as Code (IaC)
- Checkov or tfsec running on infrastructure changes
- Critical misconfigurations (public buckets, open security groups) blocked
- Intentional exceptions documented with skip comments
SBOM (Software Bill of Materials)
- SBOM generated for each release
- SBOMs scanned for vulnerabilities
- License policy defined (acceptable/blocked licenses)
- SBOMs stored with release artifacts
Dynamic testing (DAST)
- Baseline scan running on staging
- Security headers checked
- Full scan scheduled (weekly or pre-release)
Finding response process
- Finding response playbook documented
- Escalation matrix defined
- Risk acceptance process established
- Finding documentation template in use
Process
- Someone owns security finding triage
- Severity thresholds defined
- Metrics being tracked
Check off at least 18 of 28 items before moving on.
Security scanning tools reference
This section provides a comprehensive list of security scanning tools, organized from free/simple to enterprise/complex. Start with the free tier — it covers most needs. Move to paid tools when you need specific features, better support, or enterprise compliance.
SAST tools (static code analysis)
| Tool | Price | Languages | Best for | Link |
|---|---|---|---|---|
| Semgrep | Free (open source) | 30+ languages | General-purpose, custom rules | semgrep.dev |
| Bandit | Free (open source) | Python | Python-specific security | GitHub |
| gosec | Free (open source) | Go | Go-specific security | GitHub |
| Brakeman | Free (open source) | Ruby/Rails | Rails applications | brakemanscanner.org |
| ESLint security | Free (open source) | JavaScript | JS/Node.js security rules | GitHub |
| Psalm | Free (open source) | PHP | PHP type checking + security | psalm.dev |
| PHPStan | Free (open source) | PHP | PHP static analysis | phpstan.org |
| CodeQL | Free for public repos | 10+ languages | Deep semantic analysis | GitHub |
| SonarQube Community | Free (self-hosted) | 30+ languages | Code quality + security | sonarsource.com |
| Semgrep Pro | Paid | 30+ languages | Advanced rules, team features | semgrep.dev |
| SonarCloud | Free for public, paid for private | 30+ languages | Cloud-hosted SonarQube | sonarcloud.io |
| Snyk Code | Free tier, paid plans | 10+ languages | AI-powered, fast | snyk.io |
| Checkmarx | Enterprise | 30+ languages | Enterprise compliance | checkmarx.com |
| Veracode | Enterprise | 30+ languages | Enterprise, binary analysis | veracode.com |
| Fortify | Enterprise | 30+ languages | Enterprise, on-premise | microfocus.com |
Recommendation for small teams: Start with Semgrep (free, fast, good rules). Add CodeQL if you're on GitHub. Consider Snyk Code if you need more coverage.
SCA tools (dependency scanning)
| Tool | Price | Ecosystems | Best for | Link |
|---|---|---|---|---|
| npm audit | Free (built-in) | npm | Node.js projects | docs.npmjs.com |
| pip-audit | Free (open source) | pip | Python projects | GitHub |
| bundle-audit | Free (open source) | bundler | Ruby projects | GitHub |
| govulncheck | Free (official) | Go modules | Go projects | pkg.go.dev |
| composer audit | Free (built-in) | Composer | PHP projects | getcomposer.org |
| Dependabot | Free (GitHub built-in) | 15+ ecosystems | GitHub repos | GitHub |
| Trivy | Free (open source) | All major + containers | Universal scanner | trivy.dev |
| Grype | Free (open source) | All major + containers | Fast, SBOM support | GitHub |
| OSV-Scanner | Free (Google) | All major | Google's OSV database | GitHub |
| OWASP Dependency-Check | Free (open source) | Java, .NET, JS, Ruby | OWASP project | owasp.org |
| Renovate | Free (open source) | 50+ ecosystems | Dependency updates | GitHub |
| Snyk Open Source | Free tier, paid plans | All major | Best fix suggestions | snyk.io |
| Mend (WhiteSource) | Paid | All major | License compliance | mend.io |
| Black Duck | Enterprise | All major | Enterprise compliance | synopsys.com |
| JFrog Xray | Paid | All major | Artifact repository integration | jfrog.com |
Recommendation for small teams: Use Dependabot (free on GitHub) or Renovate. Add Trivy for containers. Snyk free tier is great if you need more visibility.
DAST tools (dynamic testing)
| Tool | Price | Type | Best for | Link |
|---|---|---|---|---|
| OWASP ZAP | Free (open source) | Full DAST | General web app testing | zaproxy.org |
| Nuclei | Free (open source) | Template-based | Fast, specific CVE checks | nuclei.projectdiscovery.io |
| Nikto | Free (open source) | Web server scanner | Server misconfigurations | GitHub |
| wapiti | Free (open source) | Web app scanner | Black-box testing | GitHub |
| Arachni | Free (open source) | Full DAST | Feature-rich scanner | GitHub |
| sqlmap | Free (open source) | SQL injection | SQL injection testing | sqlmap.org |
| StackHawk | Free tier, paid plans | API-focused | Modern APIs, CI/CD native | stackhawk.com |
| Burp Suite Community | Free | Manual testing | Manual security testing | portswigger.net |
| Burp Suite Pro | $449/year | Full DAST | Professional pen testing | portswigger.net |
| Acunetix | Paid | Full DAST | Automated web scanning | acunetix.com |
| Invicti (Netsparker) | Enterprise | Full DAST | Enterprise web apps | invicti.com |
| Qualys WAS | Enterprise | Full DAST | Enterprise, compliance | qualys.com |
Recommendation for small teams: Start with ZAP baseline scans in CI. Add Nuclei for CVE-specific checks. Burp Suite Community for manual testing.
Container and image scanning
| Tool | Price | Features | Best for | Link |
|---|---|---|---|---|
| Trivy | Free (open source) | Images, IaC, SBOM | Universal scanner | trivy.dev |
| Grype | Free (open source) | Images, SBOM | Fast, Anchore ecosystem | GitHub |
| Clair | Free (open source) | Images | Container registries | GitHub |
| Syft | Free (open source) | SBOM generation | Software Bill of Materials | GitHub |
| Docker Scout | Free tier | Images | Docker Hub integration | docker.com |
| Snyk Container | Free tier, paid plans | Images, Kubernetes | Fix recommendations | snyk.io |
| Anchore Enterprise | Paid | Images, policies | Enterprise compliance | anchore.com |
| Sysdig Secure | Paid | Runtime + scanning | Runtime security | sysdig.com |
| Prisma Cloud | Enterprise | Full CNAPP | Cloud-native security | paloaltonetworks.com |
Recommendation for small teams: Trivy does everything you need for free. Add Docker Scout if you're on Docker Hub.
Infrastructure as Code (IaC) scanning
| Tool | Price | Supported IaC | Best for | Link |
|---|---|---|---|---|
| Checkov | Free (open source) | Terraform, CloudFormation, K8s, ARM | Comprehensive IaC scanning | checkov.io |
| tfsec | Free (open source) | Terraform | Terraform-focused | GitHub |
| Terrascan | Free (open source) | Terraform, K8s, Helm, Dockerfile | Policy as code | GitHub |
| KICS | Free (open source) | 15+ IaC platforms | Broad coverage | kics.io |
| cfn-lint | Free (AWS official) | CloudFormation | AWS CloudFormation | GitHub |
| cfn_nag | Free (open source) | CloudFormation | CloudFormation security | GitHub |
| kubesec | Free (open source) | Kubernetes | K8s manifest security | kubesec.io |
| Trivy | Free (open source) | Terraform, K8s, Dockerfile | Universal (IaC + containers) | trivy.dev |
| Snyk IaC | Free tier, paid plans | Terraform, K8s, CloudFormation | Fix recommendations | snyk.io |
| Bridgecrew | Paid (now Prisma Cloud) | All major | Enterprise, Checkov backend | paloaltonetworks.com |
Recommendation for small teams: Start with Checkov — it's comprehensive and free. tfsec is great if you only use Terraform.
IaC scanning in CI:
# GitHub Actions - Checkov
- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
soft_fail: false
# GitLab CI - tfsec
tfsec:
stage: test
image: aquasec/tfsec:latest
script:
- tfsec terraform/ --severity-override=HIGH,CRITICAL
SBOM generation and analysis
| Tool | Price | Type | Best for | Link |
|---|---|---|---|---|
| Syft | Free (open source) | SBOM generation | Universal generator | GitHub |
| cdxgen | Free (open source) | SBOM generation | CycloneDX format | GitHub |
| Trivy | Free (open source) | SBOM + scanning | All-in-one | trivy.dev |
| Grype | Free (open source) | SBOM vulnerability scan | Fast scanning | GitHub |
| OSV-Scanner | Free (Google) | SBOM vulnerability scan | Google's database | GitHub |
| OWASP Dependency-Track | Free (self-hosted) | SBOM platform | Full management | dependencytrack.org |
| FOSSA | Free tier, paid plans | License compliance | Deep license analysis | fossa.com |
| Anchore Enterprise | Paid | SBOM platform | Enterprise policies | anchore.com |
| Sonatype Nexus | Paid | SBOM + compliance | Repository integration | sonatype.com |
Recommendation for small teams: Use Syft to generate SBOMs, Grype to scan for vulnerabilities. Add OWASP Dependency-Track if you need centralized tracking.
Secret scanning
| Tool | Price | Features | Best for | Link |
|---|---|---|---|---|
| gitleaks | Free (open source) | Git history, pre-commit | Fast, configurable | gitleaks.io |
| truffleHog | Free (open source) | Git, S3, GCS, verified secrets | Deep scanning | GitHub |
| git-secrets | Free (AWS) | Pre-commit hooks | AWS credentials | GitHub |
| detect-secrets | Free (open source) | Baseline approach | Yelp's tool | GitHub |
| GitHub Secret Scanning | Free (GitHub built-in) | Push protection | GitHub repos | docs.github.com |
| GitLab Secret Detection | Free (GitLab built-in) | Pipeline integration | GitLab repos | docs.gitlab.com |
| GitGuardian | Free for individuals | Real-time monitoring | Developer-friendly | gitguardian.com |
| Snyk | Free tier | Part of Snyk Code | Unified platform | snyk.io |
Recommendation for small teams: Enable GitHub/GitLab built-in scanning. Add gitleaks as pre-commit hook. See the Secrets management chapter for detailed setup.
API security testing
| Tool | Price | Type | Best for | Link |
|---|---|---|---|---|
| OWASP ZAP | Free (open source) | OpenAPI/Swagger scanning | REST APIs | zaproxy.org |
| Nuclei | Free (open source) | Template-based | API CVE checks | nuclei.projectdiscovery.io |
| Postman | Free tier | API testing platform | Manual + automated | postman.com |
| StackHawk | Free tier, paid plans | API-first DAST | Modern APIs, GraphQL | stackhawk.com |
| Akto | Free tier | API discovery + testing | API inventory | akto.io |
| 42Crunch | Paid | OpenAPI security | API security platform | 42crunch.com |
| Salt Security | Enterprise | Runtime API protection | API threat detection | salt.security |
| Noname Security | Enterprise | Full API security | Enterprise APIs | nonamesecurity.com |
Recommendation for small teams: Use ZAP with OpenAPI import. Add StackHawk if you need better API support.
All-in-one platforms
These platforms combine multiple scanning types in one solution:
| Platform | Includes | Price | Best for | Link |
|---|---|---|---|---|
| GitHub Advanced Security | SAST (CodeQL), SCA, Secrets | $49/user/month | GitHub-native teams | github.com |
| GitLab Ultimate | SAST, SCA, DAST, Secrets, Container | $99/user/month | GitLab-native teams | gitlab.com |
| Snyk | SAST, SCA, Container, IaC | Free tier, paid plans | Developer-friendly | snyk.io |
| Sonar | SAST, code quality | Free tier (Cloud), paid | Code quality focus | sonarsource.com |
| Veracode | SAST, SCA, DAST | Enterprise | Large enterprises | veracode.com |
| Checkmarx One | SAST, SCA, DAST, IaC | Enterprise | Enterprise compliance | checkmarx.com |
| Synopsys | SAST, SCA, DAST | Enterprise | Enterprise, legacy | synopsys.com |
Recommendation for small teams: If you're on GitHub, enable free features first, then consider GitHub Advanced Security. Snyk's free tier is generous and covers most needs. Avoid enterprise platforms until you actually need enterprise features.
Quick start: minimal viable security pipeline
If you're just starting, here's the minimum setup using only free tools:
| Category | Tool | Why |
|---|---|---|
| SAST | Semgrep | Fast, good rules, free |
| SCA | Dependabot or Trivy | Built-in or universal |
| Secrets | gitleaks | Pre-commit + CI |
| Containers | Trivy | Scans everything |
| IaC | Checkov | Comprehensive, free |
| SBOM | Syft + Grype | Generate + scan |
| DAST | ZAP Baseline | Standard, reliable |
Total cost: $0. Setup time: 3-4 hours.
Further reading
- Semgrep documentation
- Snyk Learn — Free security training
- OWASP DevSecOps Guideline
- GitHub security features
- GitLab security scanning
- Trivy documentation
- Checkov documentation
- OWASP Testing Guide
What's next
You've integrated security scanning into your CI/CD pipeline. Next chapter: container and cloud infrastructure security — securing Docker images, cloud configurations, and Infrastructure as Code.