## The DevSecOps Reality: Security at Development Speed
Teams ship several times a day. Manual security review doesn't run at that speed, so it either becomes the bottleneck or it quietly gets skipped — and the findings that show up after the merge are the expensive ones.
I've been building security automation for enterprise environments for years, and the failure mode is always the same. The tools run fine. Developers stop reading the output. Too many false positives, too many findings that have nothing to do with the change in front of them.
The version of this I remember best was a financial-services platform whose scan pipeline had quietly stopped being useful — jobs coupled together so one tool's failure took the whole run down with it, and by then nobody was reading the results anyway. Rebuilding it meant making every scanner an independent job and letting change detection decide what ran at all. Same tools. The difference was that the output became trustworthy enough to act on.
So I built this GitHub Actions scanning suite around that problem. It only scans what changed, it covers Python, TypeScript, and Terraform, and it puts findings in GitHub's Security tab where developers are already reading code.
## Solution Architecture: One Workflow, Parallel Jobs
It's one workflow that fans out into per-stack jobs and converges on a single summary. The diagram below traces it from trigger event through to the GitHub Security tab:
Push/PR/Manual] --> B[📋 Setup & Change Detection
Decide what needs scanning] %% Setup Job Output B --> C[🎯 Execution Plan
Determines which scans to run] %% Independent Parallel Jobs C --> D{Backend
Scan Needed?} C --> E{API
Scan Needed?} C --> F{Frontend
Scan Needed?} C --> G{Infrastructure
Scan Needed?} C --> H{Security
Scan Needed?} %% Independent Scan Jobs D -->|Yes| D1[🐍 Backend Python Scan
Creates backend_results] D -->|No| D2[⏭️ Skip Backend] E -->|Yes| E1[📡 API TypeScript Scan
Creates api_results] E -->|No| E2[⏭️ Skip API] F -->|Yes| F1[🎨 Frontend TypeScript Scan
Creates frontend_results] F -->|No| F2[⏭️ Skip Frontend] G -->|Yes| G1[🏗️ Infrastructure Security Scan
Creates infrastructure_results] G -->|No| G2[⏭️ Skip Infrastructure] H -->|Yes| H1[🔐 Comprehensive Security Scan
Creates security_results] H -->|No| H2[⏭️ Skip Security] %% Independent Artifact Upload D1 --> D3[📤 Upload Backend Artifacts
backend-results-timestamp] E1 --> E3[📤 Upload API Artifacts
api-results-timestamp] F1 --> F3[📤 Upload Frontend Artifacts
frontend-results-timestamp] G1 --> G3[📤 Upload Infrastructure Artifacts
infrastructure-results-timestamp] H1 --> H3[📤 Upload Security Artifacts
security-results-timestamp] %% Summary Job Convergence D3 --> I[📊 Generate Summary
Download all individual artifacts] E3 --> I F3 --> I G3 --> I H3 --> I D2 --> I E2 --> I F2 --> I G2 --> I H2 --> I %% Enhanced Summary Processing I --> J[🔍 Enhanced Results Analysis
Parse findings from all scan formats] J --> K[📄 Generate Actionable Summary
Security/quality insights with priorities] %% GitHub Integration K --> L[📤 Upload Summary Artifact
comprehensive-summary-timestamp] L --> M{Is Pull Request?} M -->|Yes| N[💬 Add PR Comment
Status summary with links] M -->|No| N1[⏭️ Skip PR Comment] L --> O{SARIF Files Available?} O -->|Yes| P[🔐 Upload to GitHub Security Tab
Code scanning integration] O -->|No| P1[⏭️ No Security Upload] N --> Q[✅ Workflow Complete
All results available as artifacts] N1 --> Q P --> Q P1 --> Q %% Styling style A fill:#e1f5fe,stroke:#01579b,stroke-width:3px style B fill:#f3e5f5,stroke:#4a148c,stroke-width:2px style C fill:#fff3e0,stroke:#f57c00,stroke-width:2px style D1 fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px style E1 fill:#fff3e0,stroke:#e65100,stroke-width:2px style F1 fill:#fce4ec,stroke:#880e4f,stroke-width:2px style G1 fill:#e0f2f1,stroke:#004d40,stroke-width:2px style H1 fill:#ffebee,stroke:#b71c1c,stroke-width:2px style I fill:#f1f8e9,stroke:#33691e,stroke-width:2px style J fill:#e3f2fd,stroke:#0d47a1,stroke-width:2px style K fill:#e3f2fd,stroke:#0d47a1,stroke-width:2px style Q fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px
Key Architecture Benefits:
- • Independent parallel execution (no coordination failures)
- • Change detection (only scan what was touched)
- • Multi-format result parsing (JSON, SARIF, Markdown)
- • Enterprise GitHub Security tab integration
- • Actionable summary reports with priority ranking
- • Portable across most repository structures (two directories to copy)
## Change Detection: Scan What Actually Changed
Most pipelines run every scan on every push, whether or not the code in question was touched. That burns CI minutes, and worse, it trains developers to ignore the output. This one diffs the changed paths first and decides what needs to run.
# Smart change detection logic
# NOTE: requires a full-history checkout — actions/checkout@v4 with fetch-depth: 0.
# The default shallow clone (depth 1) won't have the base commit to diff against.
BASE="${{ github.event.before }}"
# On a brand-new branch, github.event.before is all zeros (no prior commit).
# Fall back to diffing against the default branch instead of erroring.
if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then
BASE="$(git rev-parse origin/${{ github.event.repository.default_branch }})"
fi
git diff --name-only "$BASE" "${{ github.sha }}" > changed_files.txt
# Backend changes trigger Python security analysis
if grep -E '^(backend|src|app)/' changed_files.txt; then
echo "backend_scan=true" >> "$GITHUB_OUTPUT"
fi
# Frontend changes trigger TypeScript/JavaScript analysis
if grep -E '^(frontend|web|client|ui)/' changed_files.txt; then
echo "frontend_scan=true" >> "$GITHUB_OUTPUT"
fi
# Infrastructure changes trigger Terraform security scans
if grep -E '^(infra|terraform|infrastructure)/' changed_files.txt; then
echo "infrastructure_scan=true" >> "$GITHUB_OUTPUT"
fi
# Always run comprehensive security if workflow files change
if grep -E '^\.github/workflows/' changed_files.txt; then
echo "force_full_scan=true" >> $GITHUB_OUTPUT
fi
The change detection logic supports multiple directory naming conventions and automatically triggers full scans when workflow configurations change, ensuring that security updates to the scanning suite itself are properly validated.
Change Detection Patterns
- •
backend/,src/,app/→ Python scans - •
frontend/,web/,client/→ TypeScript scans - •
api/,services/→ API-specific scans - •
infra/,terraform/→ Infrastructure scans - •
.github/workflows/→ Force full scan
Override Capabilities
- • Manual workflow dispatch with scope selection
- • Force full scan option
- • Security-only or quality-only modes
- • Component-specific scan triggers
- • Branch-based execution policies
## Multi-Language Security Analysis
Almost nobody ships one language. This suite covers Python, TypeScript/JavaScript, and Terraform, and each one gets the tools that actually fit it rather than a lowest-common- denominator scan.
Python Security Stack
# Python security scanning pipeline
# Code formatting and style consistency
black --check --diff backend/
# Code quality and style issues
# NOTE: flake8 ships only two formatters, `default` and `pylint` — `--format=json`
# needs the flake8-json plugin installed, or this fails.
flake8 backend/ --format=json --output-file=flake8-results.json
# Security vulnerability analysis
bandit -r backend/ -f json -o bandit-results.json
# Dependency vulnerability scanning (Safety CLI 3.x uses `scan`; `check` is legacy).
# `scan` requires authentication (`safety auth` / SAFETY_API_KEY in CI).
safety scan --json > safety-results.json
# Advanced security pattern detection
# Pin a ruleset (e.g. p/default) rather than --config=auto, which auto-selects
# rules via the Semgrep registry and reports project metadata — avoid that in
# private/enterprise repos. `semgrep scan` is the current subcommand.
semgrep scan --config=p/default backend/ --json --output=semgrep-results.json
TypeScript/JavaScript Analysis
# TypeScript security and quality analysis
# TypeScript compilation and type checking
tsc --noEmit --strict --skipLibCheck
# ESLint with security rules and custom configurations
eslint frontend/ --ext .ts,.tsx,.js,.jsx --format json \
--output-file eslint-results.json
# Advanced pattern matching for security issues
eslint frontend/ --ext .ts,.tsx,.js,.jsx \
--config .eslintrc.security.js --format json
Infrastructure Security (Terraform)
# Infrastructure security scanning suite
# Multi-tool Terraform security analysis via tfscan.sh
./security/tfscan.sh --directory terraform/ --output-dir results/
# Tools included in tfscan.sh:
# - tfsec: AWS/Azure/GCP security issues
# - checkov: Policy-as-code violations
# - terrascan: Compliance and security rules
# - tflint: Terraform syntax and logic issues
#
# DATED, as of 2026: tfsec was folded into Trivy (no new checks — use `trivy config`),
# and Terrascan was archived in November 2025. If you're building this now, checkov
# + trivy + tflint covers the same ground with tools that still ship updates.
🔧 Professional Security Tools Included:
The wrapper scripts doing the actual work:
- • tfscan.sh - Terraform scanner wrapping four complementary tools
- • lint_python.py - Enhanced Python analysis with security focus
- • security_scan.sh - Multi-language security pattern detection
- • generate_enhanced_summary.py - Parses and ranks results
## GitHub Security Integration & SARIF Format
Every scanner writes SARIF (Static Analysis Results Interchange Format), which GitHub's Security tab reads directly. That's the piece that gets findings in front of developers instead of burying them in a CI log nobody opens.
# GitHub Security tab integration
# upload-sarif needs the security-events: write permission
permissions:
security-events: write
steps:
# Stage every tool's SARIF output into one directory first...
# (semgrep-results.sarif, bandit-results.sarif, tfsec-results.sarif, etc.)
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3 # v3 — v2 is deprecated
if: always()
with:
# sarif_file takes a single .sarif file OR a directory of them — not a list
sarif_file: sarif-results/
category: comprehensive-security-scan
# Results appear in Security → Code scanning alerts
# Includes file locations, severity levels, and remediation guidance
A couple of details that trip people up: upload-sarif requires the
security-events: write permission, and its sarif_file input takes a
single file or a directory — not a list. So the pattern is to write each
tool's SARIF output into one folder (here, sarif-results/) and point the action
at that folder. Use @v3; the older @v2 major is deprecated.
Once findings land there they behave like any other GitHub alert, which is also where the audit trail comes from.
Security Tab Features
- • Automatic security alert generation
- • File-level issue tracking with line numbers
- • Severity classification (Critical/High/Medium/Low)
- • Historical trend analysis
- • Integration with GitHub Advanced Security
Enterprise Benefits
- • Centralized security dashboard
- • Compliance audit trail
- • Developer-friendly issue tracking
- • Integration with security policies
- • Automated remediation guidance
## Enhanced Summary Generation & Intelligence
Raw scanner output is a wall of text, and every tool formats it differently. The summary step parses all of those formats and ranks what came back, so the answer to "what do I fix first" is at the top of the job summary.
# Enhanced summary generation logic
# Multi-format result parsing
def parse_scan_results(results_dir):
findings = []
# Parse JSON results (Bandit, ESLint, Flake8)
for json_file in glob.glob(f"{results_dir}/**/*.json", recursive=True):
findings.extend(parse_json_results(json_file))
# Parse SARIF results (Semgrep, tfsec, Checkov)
for sarif_file in glob.glob(f"{results_dir}/**/*.sarif", recursive=True):
findings.extend(parse_sarif_results(sarif_file))
# Parse Markdown results (custom tools)
for md_file in glob.glob(f"{results_dir}/**/*.md", recursive=True):
findings.extend(parse_markdown_results(md_file))
return prioritize_findings(findings)
The summary generator provides executive-level insights, developer-focused recommendations, and automated priority classification based on severity levels, affected file counts, and security impact assessment.
📊 Summary Report Features:
- • Executive overview with finding counts and severity breakdown
- • Top 10 priority issues with file locations and remediation steps
- • Component-by-component analysis with detailed breakdowns
- • Actionable recommendations based on actual scan findings
- • Tool integration status and execution metadata
- • Error analysis and troubleshooting guidance
## Production Deployment & Portability
Setup is two directories and a chmod. It moves between repositories cleanly as
long as your layout resembles one of the conventions below; anything stranger than that
needs the detection patterns adjusted, which is a few lines of grep.
Two-Directory Setup
# Minimal setup for any repository
# Copy required directories to your repository
cp -r source/.github/workflows .github/
cp -r source/security security/
# Set executable permissions
chmod +x security/*.sh
chmod +x security/validation/*.sh
# Validate installation
./security/validation/test-portability.sh --verbose
# Ready to scan - no additional configuration required
Automatic Technology Detection
The workflow reads the project layout and decides which scans apply, so there's no config file to maintain. It recognizes the common directory conventions; anything unusual needs the patterns edited.
Supported Project Structures
Backend Detection:
- •
backend/,src/,server/,app/ - • Python files:
*.py - • Package files:
requirements.txt,poetry.lock
Frontend Detection:
- •
frontend/,web/,client/,ui/ - • TypeScript/JS:
*.ts,*.tsx,*.js,*.jsx - • Config files:
package.json,tsconfig.json
Enterprise Integration Patterns
- ▶ Branch Protection Rules: Require successful scans before merge
- ▶ GitHub Advanced Security: Full integration with GHAS features
- ▶ Compliance Reporting: Automated audit trails and security metrics
- ▶ CI/CD Integration: Compatible with existing deployment pipelines
## Real-World Enterprise Impact
Two things change once this is running. Issues get caught while the author still has the code in their head, and every change gets the same scan instead of whichever ones somebody remembered to look at.
Security Posture Improvements
- ▶ Early Detection: Security issues identified during development, not production
- ▶ Consistent Analysis: Every code change receives comprehensive security review
- ▶ Vulnerability Tracking: Centralized security findings with remediation guidance
Development Velocity Benefits
- ▶ Scoped Scanning: Only relevant components analyzed, reducing CI/CD time
- ▶ Developer-Friendly: Clear, actionable feedback with file locations and fixes
- ▶ Reduced Context Switching: Security feedback integrated into existing workflows
Operational Excellence
Because every run uploads its artifacts and SARIF, the audit trail is a byproduct rather than a separate reporting exercise — which is usually what governance teams are actually asking for.
## Wrapping Up
That's the whole suite: change detection up front, per-language scanners running in parallel, and one summary at the end that says what to fix first.
If you're standing up something similar, start small: wire up one scanner per language, get the signal-to-noise right, then add change detection so you're only scanning what actually changed. The orchestration is the easy part — the hard part is tuning it so developers trust the results instead of ignoring them.
Related reading: why this matters even more in the age of AI-generated code — The Security Side of Vibe Coding.
Donny Schreiber
Cloud and product security engineer at AWS, based in Boulder, Colorado. I write about cloud security, DevSecOps, infrastructure-as-code, and the security side of AI — drawn from daily practice.
The Security Side of Vibe Coding: What AI-Generated Code Gets Wrong
An honest look at the security risks of AI-assisted coding — real incidents, real examples, and practical guardrails.
Building Hierarchical, Multi-Region AWS VPC IPAM with Terraform
Hierarchical, multi-Region IP address management on AWS VPC IPAM, automated in Terraform — 67 pools and a Streamlit planning tool.