DEV Community

Cover image for Building a Practical DevSecOps Pipeline: From Basic Security to Enterprise-Style Protection
Rajesh Pethe
Rajesh Pethe

Posted on

Building a Practical DevSecOps Pipeline: From Basic Security to Enterprise-Style Protection

Hello folks!

So you've got your CI/CD pipeline running smoothly, but now you're missing some security scanning to your codebase. I recently took a basic security workflow and enhanced it, this is my journey building an enterprise-style pipeline including:

  • scanning for secrets with GitGuardian and TruffleHog
  • Reporting vulnerabilities using Bandit, Semgrep and Safety
  • Scanning vulnerabilities in Python dependencies using Snyk
  • Licensing and Compliance scan using FOSSA
  • Checkov IaC Security Scan to find vulnerabilities in Docker, Kubernetes and Terraform specs
  • Container security scan using Trivy and Docker Scout
  • Dynamic security testing for API endpoints

Let me show you exactly how I did it, and more importantly, why each piece matters.

Basic but Not Enough

Most of us start with something like this in their GitHub Actions:

- name: Run Bandit (SAST)
  run: bandit -r .

- name: Snyk Dependency Scan
  uses: snyk/actions/python@master

- name: Trivy Container Scan
  uses: aquasecurity/trivy-action@master
Enter fullscreen mode Exit fullscreen mode

This covers the basics - some static analysis, dependency scanning, and container security. But it's not enough for real-world applications. You're missing secrets detection, infrastructure security, proper quality gates, and a bunch of other stuff that might bite you later.

Note: Snyk Dependency Scan can be slow if your codebase has large dependency tree and you are using a free account.

The Enhanced Security Workflow:

I've created the "enhanced security workflow" that covers pretty much every security scanning angle I could think of. Let's dive into each section and understand why each piece is crucial.

1. Security-First Permissions

permissions:
  contents: read
  security-events: write
  actions: read
Enter fullscreen mode Exit fullscreen mode

Why this matters: By default, GitHub Actions gets too many permissions. This is the principle of least privilege in action - only give what's absolutely necesary. The security-events: write permission is what lets us upload SARIF reports to GitHub's security tab.

2. Secret Detection

This is probably the most important addition. You might have seen developers accidentally commit API keys, database passwords, or AWS credentials.

- name: GitGuardian Security Scan
  uses: GitGuardian/ggshield/actions/secret@v1.25.0
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}

- name: TruffleHog OSS Secret Scanning
  uses: trufflesecurity/trufflehog@main
  with:
    path: ./services/upload_service
    base: ${{ github.event.repository.default_branch }}
    head: HEAD
    extra_args: --debug --only-verified
Enter fullscreen mode Exit fullscreen mode

What's happening here:

  • GitGuardian knows about 450 types of secrets and has really low false positives.
  • TruffleHog is the open-source alternative that's really good
  • The --only-verified flag means it'll only alert on secrets it can actually verify (like testing if an API key actually works)

Tip: Run both! GitGuardian might catch something TruffleHog misses and vice versa.

Note: Either or both should be in your pre-commit hook as well to catch secrets at the earliest possible stage.

3. Enhanced SAST (Static Application Security Testing)

Instead of just running Bandit, we're going to add couple more tools:

- name: Install Security Tools
  run: |
    pip install bandit[toml] safety semgrep

- name: Run Bandit SAST (Enhanced)
  working-directory: ./services/upload_service
  run: |
    bandit -r . -f json -o bandit-report.json || true
    bandit -r . -f txt
  continue-on-error: true

- name: Semgrep Security Analysis
  uses: semgrep/semgrep-action@v1
  with:
    config: >-
      p/security-audit
      p/python
      p/owasp-top-ten
  env:
    SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

- name: Safety Check (Python Dependencies)
  working-directory: ./services/upload_service
  run: safety check --json --output safety-report.json || true
Enter fullscreen mode Exit fullscreen mode

Breaking this down:

  • Bandit is still our Python-specific security scanner, but now we're saving reports in both JSON (for processing) and text (for human reading) - Note: This will run bandit twice, adjust accordingly.
  • Semgrep is new SAST tool - it's got rules for OWASP Top 10, language-specific issues, and general security patterns
  • Safety checks your Python dependencies against known vulnerability databases

Why multiple tools? Each tool has its strengths. Bandit knows Python really well, Semgrep has broader coverage, and Safety focuses specifically on dependencies. It's like having multiple security experts scan your code. Some of us might declare this an over kill, but there is for us understand, evaluate and choose the best combination.

4. Infrastructure as Code Security (Dockerfiles Matter Too)

- name: Checkov IaC Security Scan
  uses: bridgecrewio/checkov-action@master
  with:
    directory: .
    framework: dockerfile,kubernetes,terraform
    output_format: sarif
    output_file_path: checkov-report.sarif
    quiet: true
    soft_fail: true
Enter fullscreen mode Exit fullscreen mode

This is very important step! Checkov scans your Docker files, Kubernetes manifests, Terraform configs - basically any infrastructure-as-code you've got. It'll catch stuff like:

  • Running containers as root (big no-no)
  • Mandatory Health checks in containers
  • Missing security contexts in Kubernetes
  • Overly permissive IAM policies in Terraform
  • Secrets hardcoded in Docker files

The soft_fail: true means it won't break your build, but it'll still report issues.

5. The Quality Gate

This step decides if the workflow fails or not. Instead of just running scans and hoping someone reads the reports, this implements automated decision-making:

def check_security_reports():
    critical_issues = 0
    high_issues = 0

    # Check Bandit report
    try:
        with open('services/upload_service/bandit-report.json') as f:
            bandit_data = json.load(f)
            for result in bandit_data.get('results', []):
                if result['issue_severity'] == 'HIGH':
                    high_issues += 1
                elif result['issue_severity'] == 'MEDIUM':
                    critical_issues += 1
    except FileNotFoundError:
        print("Bandit report not found")

    # Security gate logic
    if critical_issues > 0:
        print(f"❌ SECURITY GATE FAILED: {critical_issues} critical security issues found")
        sys.exit(1)
    elif high_issues > 5:
        print(f"⚠️  WARNING: {high_issues} high-severity issues found")
    else:
        print("✅ SECURITY GATE PASSED: No critical security issues detected")
Enter fullscreen mode Exit fullscreen mode

This is where the magic happens! The pipeline will actually fail if there are critical security issues. Fix it now or your deployment won't happen.

You can customize these thresholds based on your risk tolerance. Maybe you allow 0 critical issues in production but 3 in development branches.

6. Container Security

We're not just scanning the final image anymore:

- name: Trivy Filesystem Scan
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: './services/upload_service'

- name: Trivy Container Image Scan (Critical)
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'upload-service:latest'
    exit-code: '1'
    severity: 'CRITICAL,HIGH'

- name: Docker Scout CVE Scan
  uses: docker/scout-action@v1
  with:
    command: cves
    image-ref: upload-service:latest
    only-severities: critical,high
Enter fullscreen mode Exit fullscreen mode

Three layers of container security:

  1. Filesystem scan - checks your source code and files
  2. Image scan - scans the built Docker image for vulnerabilities
  3. Docker Scout - Docker's own security scanning (different vulnerability database)

The exit-code: '1' on the image scan means it'll fail the build if critical or high severity issues are found.

7. Dynamic Security Testing (The Runtime Check)

- name: Start Application for DAST
  run: |
    docker-compose up -d
    sleep 30
    curl -f http://localhost:8000/health || exit 1

- name: OWASP ZAP API Scan
  uses: zaproxy/action-api-scan@v0.7.0
  with:
    target: 'http://localhost:8000'
    format: 'openapi'
Enter fullscreen mode Exit fullscreen mode

DAST (Dynamic Application Security Testing) is where we actually run the application and poke at it to see if there are vulnerabilities that only show up at runtime. OWASP ZAP is like having a hacker test your API for common web vulnerabilities.

The Secret Sauce: SARIF Integration

You'll notice we're outputting a lot of reports in SARIF format:

- name: Upload Trivy SARIF Reports
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: |
      trivy-fs-results.sarif
      trivy-image-results.sarif
Enter fullscreen mode Exit fullscreen mode

SARIF (Static Analysis Results Interchange Format) is a standard format that GitHub understands. When you upload SARIF files, all your security findings show up beautifully in GitHub's Security tab. No more digging through build logs!

Common Gotchas and How to Avoid Them

1. False Positives

Every security tool produces false positives. Here's how to handle them:

  • Start with continue-on-error: true while you tune your thresholds
  • Create suppresion files for known false positives
  • Use multiple tools to cross-verify findings

2. Secrets Management for Security Tools

You'll need API tokens for most of these tools:

SNYK_TOKEN
GITGUARDIAN_API_KEY  
SEMGREP_APP_TOKEN
FOSSA_API_KEY
SAFETY_API_KEY
Enter fullscreen mode Exit fullscreen mode

Store these as GitHub secrets, obviously. Most tools have free tiers that are perfect for getting started.

3. Performance Impact

This workflow is comprehensive but it's also slow. Here's how to optimize:

  • Snyk is particularly slow if you use a free account
  • Run heavy scans only on main branch pushes and PRs
  • Use caching for tool installations
  • Run some scans in parallel when possible

4. Developer Experience

Nobody likes pipelines that break all the time. Make it developer-friendly:

  • Clear error messages in quality gates
  • Easy-to-find security reports
  • Documentation on how to fix common issues

What's Next?

This setup covers most core security checks, and here are some steps to take it even further:

  1. Auto-remediation: Use Dependabot to automatically fix dependency vulnerabilities
  2. Security notifications: Integrate with Slack/Teams for security alerts
  3. Security metrics: Track MTTR (Mean Time to Remediation) and security fixes

Final Thoughts

This pipeline isn't just about adding lots of tools - it's about enforcing policies. This workflow gives developers immediate feedback on security issues while maintaining development velocity.

This is not perfect, but significantly better security is totally achievable with the right tooling and processes. You can pick and choose, tune it for your specific needs, and gradually level up your security.

The goal isn't to make development slower - it's to catch issues early when they're easy to fix, rather than in production when they're expensive and embarrassing.

Happy coding, speedy deployments! 🚀


Top comments (0)