This project builds a complete DevSecOps pipeline where security testing is automatic on every code change — not a manual process before quarterly releases. Every pull request triggers four layers of security scanning before any deployment can happen. This is the pipeline that gets DevSecOps engineers hired at CRED and Razorpay. When an interviewer asks 'how do you shift security left?' — you describe this pipeline. Developer pushes code | v GitHub Actions triggers | +------+-------+-------+-------+ | | | | | v v v v v Tests Semgrep Checkov Trivy OWASP ZAP (unit) (SAST) (IaC) (image) (DAST) | | | | | +------+-------+-------+-------+ | Security Gate (CRITICAL findings = block) (HIGH findings = warn) | v (if passed) Deploy to staging | v Deploy to production
The traditional approach — run security scans once a month, get a 200-page PDF report, hand it to developers who have already shipped 3 more features — does not work. By the time vulnerabilities are reported, the code is in production and the developer has no memory of writing it. Shift-left security means finding vulnerabilities when the developer is still looking at the code. Semgrep catches SQL injection patterns in the PR before merge. Checkov catches an S3 bucket with public-read ACL before the Terraform is applied. Trivy catches a container with a critical CVE before it is deployed. OWASP ZAP catches a missing authentication header before users can hit the endpoint.
### Step 1: Set Up the Repository Structure ```bash mkdir devsecops-demo && cd devsecops-demo mkdir -p {src,k8s,terraform,.github/workflows} ## Create a simple Node.js application with intentional vulnerabilities ## (we will fix these during the project to see the pipeline in action) cat > src/app.js << 'EOF' const express = require('express'); const { Pool } = require('pg'); const app = express(); app.use(express.json()); const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // VULNERABILITY: SQL injection — Semgrep will catch this app.get('/user', async (req, res) => { const { id } = req.query; const result = await pool.query(`SELECT * FROM users WHERE id = ${id}`); res.json(result.rows); }); // VULNERABILITY: Missing authentication — OWASP ZAP will flag this app.get('/admin/users', async (req, res) => { const result = await pool.query('SELECT * FROM users'); res.json(result.rows); }); app.get('/health', (req, res) => res.json({ status: 'healthy' })); app.listen(3000); EOF ## Create a Dockerfile with intentional vulnerability ## (using an old base image with known CVEs — Trivy will catch this) cat > Dockerfile << 'EOF' FROM node:16-alpine # OLD version with CVEs — will be flagged WORKDIR /app COPY package*.json ./ RUN npm install COPY src/ ./src/ ## VULNERABILITY: Running as root — Trivy will flag this ## Should add: USER node EXPOSE 3000 CMD ["node", "src/app.js"] EOF ## Create Terraform with intentional security issues cat > terraform/main.tf << 'EOF' ## VULNERABILITY: S3 bucket with public access — Checkov will catch this resource "aws_s3_bucket" "app_assets" { bucket = "devsecops-demo-assets" } resource "aws_s3_bucket_acl" "app_assets" { bucket = aws_s3_bucket.app_assets.id acl = "public-read" # VULNERABILITY: Public read access } ## VULNERABILITY: Security group allowing all inbound traffic resource "aws_security_group" "app" { name = "app-sg" ingress { from_port = 0 to_port = 65535 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # VULNERABILITY: All ports open } } EOF ``` ### Step 2: Configure Semgrep for SAST ```bash ## Create Semgrep configuration cat > .semgrep.yml << 'EOF' rules: # Rule 1: Detect SQL injection * id: sql-injection-string-concat patterns: * pattern: | $QUERY = "..." + $INPUT $DB.query($QUERY, ...) * pattern: | $DB.query(`...${$INPUT}...`, ...) message: | SQL Injection detected: User input $INPUT is concatenated directly into a SQL query. Use parameterised queries instead: pool.query('SELECT * FROM users WHERE id = $1', [id]) severity: ERROR languages: [javascript, typescript] metadata: cwe: CWE-89 owasp: A03:2021 # Rule 2: Detect hardcoded secrets * id: hardcoded-password patterns: * pattern: | const $VAR = "password..." * pattern: | password: "..." message: Hardcoded password detected. Use environment variables or a secrets manager. severity: ERROR languages: [javascript, typescript, yaml] # Rule 3: Detect missing authentication * id: express-route-no-auth-middleware patterns: * pattern: | app.get('/admin/...', async ($REQ, $RES) => {...}) * pattern-not: | app.get('/admin/...', $AUTH, async ($REQ, $RES) => {...}) message: Admin route missing authentication middleware. severity: WARNING languages: [javascript] EOF ## Test Semgrep locally before adding to CI npm install -g semgrep semgrep --config .semgrep.yml src/ ## Expected: Finds the SQL injection and missing auth vulnerabilities ``` ### Step 3: Configure Checkov for IaC Scanning ```bash ## Install Checkov pip install checkov ## Run Checkov on your Terraform checkov -d terraform/ --framework terraform ## Expected: Finds the public S3 bucket and overly permissive security group ## Create a custom Checkov configuration to suppress known accepted risks cat > .checkov.yml << 'EOF' soft-fail-on: * CKV_AWS_18 # Access logging on S3 — accepted for this demo bucket skip-check: * CKV_AWS_144 # Cross-region replication — not needed for demo EOF ``` ### Step 4: Build the Complete GitHub Actions Pipeline Create `.github/workflows/devsecops.yml`: ```yaml name: DevSecOps Pipeline on: push: branches: [main, develop] pull_request: branches: [main] jobs: # ============================================================ # Stage 1: Unit Tests (must pass for security scans to run) # ============================================================ unit-tests: runs-on: ubuntu-latest steps: * uses: actions/checkout@v4 * uses: actions/setup-node@v4 with: node-version: '20' * run: npm ci * run: npm test # ============================================================ # Stage 2: SAST — Semgrep static code analysis # ============================================================ sast-semgrep: runs-on: ubuntu-latest needs: unit-tests steps: * uses: actions/checkout@v4 * name: Run Semgrep SAST scan uses: returntocorp/semgrep-action@v1 with: config: > .semgrep.yml p/owasp-top-ten p/nodejs p/secrets generateSarif: true * name: Upload SARIF to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 with: sarif_file: semgrep.sarif if: always() # ============================================================ # Stage 3: IaC Security — Checkov Terraform scanning # ============================================================ iac-checkov: runs-on: ubuntu-latest needs: unit-tests steps: * uses: actions/checkout@v4 * name: Run Checkov IaC scan uses: bridgecrewio/checkov-action@v12 with: directory: terraform/ framework: terraform output_format: sarif output_file_path: checkov.sarif soft_fail: false # FAIL the pipeline on critical IaC issues check: CKV_AWS_19,CKV_AWS_20,CKV_AWS_57 # S3 and SG checks * name: Upload Checkov SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: checkov.sarif if: always() # ============================================================ # Stage 4: Build Docker image and run Trivy container scan # ============================================================ container-scan: runs-on: ubuntu-latest needs: unit-tests steps: * uses: actions/checkout@v4 * name: Build Docker image run: docker build -t devsecops-demo:${{ github.sha }} . * name: Run Trivy container vulnerability scan uses: aquasecurity/trivy-action@master with: image-ref: devsecops-demo:${{ github.sha }} format: 'sarif' output: 'trivy.sarif' severity: 'CRITICAL,HIGH' exit-code: '1' # FAIL pipeline on CRITICAL or HIGH CVEs ignore-unfixed: true # Only flag CVEs with available fixes * name: Upload Trivy SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy.sarif if: always() # ============================================================ # Stage 5: Deploy to staging (only if all security gates passed) # ============================================================ deploy-staging: runs-on: ubuntu-latest needs: [sast-semgrep, iac-checkov, container-scan] environment: staging steps: * uses: actions/checkout@v4 * name: Deploy to staging run: echo "Deploying to staging after all security gates passed" # ============================================================ # Stage 6: DAST — OWASP ZAP against running staging deployment # ============================================================ dast-owasp-zap: runs-on: ubuntu-latest needs: deploy-staging steps: * uses: actions/checkout@v4 * name: Start application for DAST scanning run: | docker run -d -p 3000:3000 \ -e DATABASE_URL=postgresql://test:test@localhost/test \ --name app devsecops-demo:${{ github.sha }} sleep 10 # Wait for app to start * name: Run OWASP ZAP DAST scan uses: zaproxy/action-baseline@v0.10.0 with: target: 'http://localhost:3000' rules_file_name: '.zap/rules.tsv' cmd_options: '-a' # Include alpha active scan rules * name: Upload ZAP report uses: actions/upload-artifact@v4 with: name: zap-report path: report_html.html if: always() ``` ### Step 5: Fix the Vulnerabilities and Watch the Pipeline Pass ```bash ## Fix 1: SQL injection — use parameterised queries cat > src/app.js << 'EOF' // FIXED: Parameterised query prevents SQL injection app.get('/user', async (req, res) => { const { id } = req.query; const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]); res.json(result.rows); }); // FIXED: Authentication middleware on admin routes const authenticate = (req, res, next) => { const token = req.headers.authorization; if (!token) return res.status(401).json({ error: 'Unauthorized' }); // verify token logic here next(); }; app.get('/admin/users', authenticate, async (req, res) => { const result = await pool.query('SELECT id, email FROM users'); // Never SELECT * res.json(result.rows); }); EOF ## Fix 2: Docker — use current Node.js and non-root user cat > Dockerfile << 'EOF' FROM node:20-alpine # Current LTS — fewer CVEs WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY src/ ./src/ ## FIXED: Run as non-root user USER node EXPOSE 3000 CMD ["node", "src/app.js"] EOF ## Fix 3: Terraform — remove public access cat > terraform/main.tf << 'EOF' resource "aws_s3_bucket" "app_assets" { bucket = "devsecops-demo-assets" } ## FIXED: Block all public access resource "aws_s3_bucket_public_access_block" "app_assets" { bucket = aws_s3_bucket.app_assets.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } ## FIXED: Minimal security group rules resource "aws_security_group" "app" { name = "app-sg" ingress { from_port = 3000 to_port = 3000 protocol = "tcp" cidr_blocks = ["10.0.0.0/8"] # Only internal VPC traffic } } EOF git add -A && git commit -m "fix: resolve all security pipeline findings" git push origin main ## Watch the pipeline — all stages should now pass ```
```bash ## 1. Introduce a vulnerability and verify the pipeline blocks it echo 'const password = "admin123";' >> src/app.js git add -A && git commit -m "test: introduce hardcoded password" git push ## Expected: Pipeline fails at sast-semgrep stage with hardcoded password finding ## 2. Verify findings appear in GitHub Security tab ## Go to your repo -> Security -> Code scanning alerts ## Expected: Semgrep, Checkov, and Trivy findings all visible ## 3. Test that CRITICAL findings block deployment ## Build with old node:12 image (has critical CVEs) docker build --build-arg NODE_VERSION=12 -t test-image . trivy image test-image --severity CRITICAL --exit-code 1 ## Expected: Non-zero exit code — pipeline would block ## 4. Verify fixed code passes all gates git revert HEAD # Remove the hardcoded password git push ## Expected: All 5 stages pass, deployment proceeds ## 5. Check ZAP report ## Download the zap-report artifact from GitHub Actions ## Open report_html.html in browser ## Expected: No FAIL-level findings, only informational warnings echo "Complete DevSecOps pipeline operational — security built into every deployment" ```
This project builds a complete DevSecOps pipeline where security testing is automatic on every code change — not a manua...
The traditional approach — run security scans once a month, get a 200-page PDF report, hand it to developers who have al...
Step 1: Set Up the Repository Structure Step 2: Configure Semgrep for SAST Step 3: Configure Checkov for IaC Scanning St...
...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.