DevSecOps with GitHub Actions: Integrating Security into Your Pipelines
CI/CD & GitOps

DevSecOps with GitHub Actions: Integrating Security into Your Pipelines

September 30, 20259 min readDevSecOpsGitHub ActionsSécurité

Security must be embedded from the first commit, not bolted on at the end. Here is how to build a complete DevSecOps pipeline with GitHub Actions.

Why DevSecOps?

According to the 2024 DORA report, high-performing engineering teams detect security issues 60 times faster when those checks are integrated into the CI/CD pipeline rather than handled as a separate gate at the end of the release cycle. The cost of fixing a vulnerability discovered in production is roughly 100 times higher than fixing the same issue at commit time. DevSecOps is not an additional burden — it is a simultaneous acceleration and risk reduction.

Snyk's 2024 State of Open Source Security report found that third-party dependencies account for over 70% of the attack surface in modern applications, and that the average time to exploit a publicly disclosed vulnerability dropped below 12 days in 2024. Waiting for a quarterly security audit is no longer a viable strategy.

GitHub Actions is the ideal platform for implementing DevSecOps because it is natively integrated with GitHub (where your code lives), offers a rich marketplace of security-focused actions, and allows you to surface security findings directly inside pull requests — the exact moment when a developer can act on them with full context.

Layer 1: Static Application Security Testing (SAST) with CodeQL

SAST analyses source code without executing it, looking for vulnerability patterns: SQL injections, cross-site scripting (XSS), insecure deserialisations, hardcoded credentials, and improper authentication logic. GitHub CodeQL is free for public repositories and included in GitHub Advanced Security for private ones.

# .github/workflows/codeql-analysis.yml
name: CodeQL Security Analysis

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * 1'   # Weekly full scan on Monday at 2am

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript, python, java
          queries: security-extended   # Includes OWASP Top 10 queries

      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
        with:
          upload: true   # Results appear in GitHub Security tab

CodeQL findings appear as inline comments on the pull request, pointing directly to the vulnerable line of code with a description of the flaw and suggested remediation. Engineers see the issue in context — not in a separate security portal days later.

Layer 2: Dependency Scanning (SCA) with Snyk

Software Composition Analysis (SCA) verifies every direct and transitive dependency against known CVE databases (NVD, OSV, GitHub Advisory). A single vulnerable transitive dependency several levels deep can expose your entire application.

- name: Audit npm dependencies
  run: npm audit --audit-level=high

- name: Snyk vulnerability scan
  uses: snyk/actions/node@master
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
  with:
    args: --severity-threshold=high --fail-on=upgradable

- name: OWASP Dependency Check
  uses: dependency-check/Dependency-Check_Action@main
  with:
    project: 'my-app'
    format: 'SARIF'
    out: 'reports'

- name: Upload dependency scan results
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: reports/dependency-check-report.sarif

The --fail-on=upgradable flag on Snyk is intentional: it only fails the pipeline for vulnerabilities where a fixed version is already available. This avoids blocking teams on vulnerabilities with no upstream fix, while ensuring that fixable issues cannot be ignored.

Layer 3: Secret Detection with Gitleaks and TruffleHog

Hardcoded secrets — API keys, database passwords, JWT signing keys, cloud credentials — are one of the most common and highest-impact vulnerability categories. Once a secret reaches a repository (even a private one), you must assume it is compromised. Detection must happen before the commit is pushed.

- name: Scan for secrets with Gitleaks
  uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: TruffleHog deep scan (verified secrets only)
  uses: trufflesecurity/trufflehog@main
  with:
    path: ./
    base: ${{ github.event.repository.default_branch }}
    head: HEAD
    extra_args: --debug --only-verified

TruffleHog's --only-verified flag is critical: it actively calls the detected credential against its provider (AWS, GitHub, Stripe, etc.) to confirm it is a live secret before failing the build. This eliminates false positives from test fixtures and example configurations.

For proactive prevention, configure a .gitleaks.toml allowlist for known false positives (test tokens, example values in documentation), and enforce the Gitleaks pre-commit hook locally using Husky or pre-commit.

Layer 4: Container Scanning with Trivy

If your application runs in Docker containers, the image itself can carry vulnerabilities in its base OS packages, language runtime, or application libraries — independently of your own code. Aqua Security's Trivy performs a comprehensive scan of the entire image filesystem.

- name: Build Docker image
  run: docker build -t app:${{ github.sha }} .

- name: Scan container image with Trivy
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: app:${{ github.sha }}
    format: sarif
    output: trivy-results.sarif
    severity: CRITICAL,HIGH
    exit-code: 1   # Fail the pipeline on CRITICAL findings
    ignore-unfixed: true   # Skip CVEs with no available fix

- name: Upload Trivy results to GitHub Security
  uses: github/codeql-action/upload-sarif@v3
  if: always()   # Upload even if the scan step failed
  with:
    sarif_file: trivy-results.sarif

The exit-code: 1 on CRITICAL severity is a hard gate: no image with a critical, unpatched vulnerability reaches your registry. Setting ignore-unfixed: true prevents blocking on CVEs where the upstream vendor has not released a patch — a situation that otherwise generates unavoidable noise in base images like node:20-slim.

Layer 5: Supply Chain Attestation (SLSA)

Supply chain attacks (SolarWinds, Codecov, 3CX) compromise the build process itself rather than the application code. SLSA (Supply-chain Levels for Software Artifacts) is a framework that establishes verifiable provenance for build artifacts — proof that a specific image was built from a specific commit by a specific pipeline, and that no intermediate step was tampered with.

- name: Build and push image
  id: push
  uses: docker/build-push-action@v5
  with:
    push: true
    tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

- name: Attest build provenance (SLSA Level 3)
  uses: actions/attest-build-provenance@v1
  with:
    subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
    subject-digest: ${{ steps.push.outputs.digest }}
    push-to-registry: true

The attestation is stored alongside the image in the registry as a signed OCI artifact. Any deployment system can verify it before running the image: gh attestation verify oci://registry/image@sha256:... --owner your-org.

Blocking Policy: What Stops the Pipeline vs. What Warns

Not every finding should block a merge with equal force. An overly aggressive policy creates alert fatigue and pressure to bypass checks; a permissive policy means security debt accumulates silently. The following policy balances both concerns:

SeverityPipeline actionNotificationResolution
CRITICALHard block — merge preventedSlack #security + auto GitHub IssueMust be fixed before merge — no exceptions
HIGHSoft block — merge preventedSecurity team notifiedMust be fixed or explicitly accepted with justification
MEDIUMWarning only — merge allowedVisible PR commentDocumented justification required in PR comment
LOWNo actionSecurity Dashboard onlyTracked passively, addressed in backlog

OIDC-Based Secret Management

The secrets used inside the pipeline (cloud credentials for deployment, registry tokens) must themselves be managed securely. The industry best practice is to eliminate long-lived stored credentials entirely using OpenID Connect (OIDC) federation.

With OIDC, GitHub Actions presents a short-lived signed token to the cloud provider (AWS, Azure, GCP), which exchanges it for temporary credentials scoped to the exact permissions needed for that pipeline run. No credentials are ever stored in GitHub Secrets, no credentials can be stolen from a fork, and no credentials need rotation.

- name: Configure AWS credentials via OIDC
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployer
    aws-region: eu-west-1
    # No stored keys — authenticated via short-lived OIDC token
    # Token is valid for 1 hour and scoped to this specific workflow run

The IAM role trust policy restricts which GitHub organisation, repository, and branch (or environment) can assume the role — a leaked OIDC token from one repository cannot be used to assume a role configured for another.

Conclusion

A complete DevSecOps pipeline with GitHub Actions — SAST, SCA, secret detection, container scanning, and supply chain attestation — can be implemented in one to two engineering days. The return on investment is immediate: vulnerabilities found at commit time cost a fraction of those found in production, and the automated paper trail satisfies compliance requirements (SOC 2, ISO 27001, PCI-DSS) with zero additional effort.

The cultural shift matters as much as the tooling: security checks embedded in the pull request workflow become part of the normal development loop, not an external audit. Engineers who see security findings in context — on their own code, at the moment they wrote it — fix them. Security integrated into the process is security that is actually respected.

← Back to blog