Introduction
following on from my original blog ‘When “Shift‑Left” Leaves the Back Door Open: Why Governance Matters More Than Ever’ this is part 2 of a 3 part follow up series where ill discuss examples of tools and strategies I’ve used to implement some of the security considerations I outlined in the aforementioned post. In part 1 I discussed Tooling, Secret Scanning, Branch and Security policies and Pre-Deployment Approvals. In this post ill be diving deeper into SAST and SCA and how you can compliment the out of the box auto enabled scanning that outputs passive results when you install GHAS with integrated build pipeline scanning which will actively fail builds on discovery of issues.
Blocking Builds with SAST and SCA scans
Out of the box the SAST component of SAST immediately starts scanning your code base for code quality e.g. looking for potential SQL injection, XSS, flawed logic, broken authentication etc and SCA scans for dependencies with Common Vulnerabilities and Exposures (CVE’s) or potential Open Source Software licensing violations.
The problem – the output of any findings is passive/informational and so in order to proactively block insecure code being merged I had to introduce SAST and SCA logic into our build pipelines. This meant that if the scans revealed any issues I would explicitly exit the build process preventing flawed code being merged or deployed. You could immediately see the result of any scans in the build console log or they could be output as a Sarif report or as build artifacts.
Below is an example of the SAST and SCA scanning introduced into a pipeline for a Javascript based solution. This was configured to run on any triggers whether it be PR or direct commits (although they were switched off which i’ll get to later). I’ve added a comment by each of the scan based tasks explaining what they do.
# ============================================================
# STAGE 1 — SECURITY SCANNING (ALWAYS RUN)
# ============================================================
stages:
- stage: SecurityScanning
displayName: "GitHub Advanced Security Scanning"
condition: always()
jobs:
- job: SecurityScan
displayName: "Run CodeQL and Dependency Scanning"
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- script: mkdir -p $(System.DefaultWorkingDirectory)/security-results
displayName: "Create security-results directory"
## AdvancedSecurity-Codeql-Init@1 task initializes the CodeQL environment and performs dependency scanning for the specified languages.
## The results are stored in the specified output directory.
- task: AdvancedSecurity-Codeql-Init@1
displayName: "Initialize CodeQL"
inputs:
languages: 'javascript'
outputDirectory: '$(System.DefaultWorkingDirectory)/security-results/dependency-scanning'
## AdvancedSecurity-Dependency-Scanning@1 task analyzes the dependencies of the project and identifies any known vulnerabilities.
- task: AdvancedSecurity-Dependency-Scanning@1
displayName: "Run Dependency Scanning (fail on high severity)"
inputs:
failOnSeverity: 'error'
## AdvancedSecurity-Codeql-Analyze@1 task runs the CodeQL analysis on the codebase to identify potential security vulnerabilities.
- task: AdvancedSecurity-Codeql-Analyze@1
displayName: "Run CodeQL Analysis"
inputs:
failOnAlerts: true
severityLevel: 'error'
## PublishBuildArtifacts@1 task publishes the security scan reports as build artifacts.
- task: PublishBuildArtifacts@1
displayName: "Publish Security Scan Reports"
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/security-results'
ArtifactName: 'security-results'
publishLocation: 'Container'
SAST Scanning Infrastructure as Code with Checkov
So I’ve written a separate section here for Infra as code SAST scanning because it doesn’t play very nicely with the out of the box GHAS CodeQL offering – hence it meant the introduction of 3rd Party Open Source Software.
There were a few options and after narrowing it down to tools that were compatible with ADO and Bicep I decided to go with Checkov as it was it was capable of scanning not only for misconfigurations, compliance (CIS, NIST, PCI, SOC2, HIPAA) and secrets, but it also looked for network configuration issues – something which was important to us as much of our IaC was made up of network components (VNETs, NAT Gateways, NSG’s private endpoints etc).
What is also really good about Checkov is that by using a .checkov.yml file to centralise the Checkov definition rules you can tell it to omit certain findings that may have resulted in false positives e.g. for things like Blob storage you may need to have public access so you don’t want this flagged and breaking the build. This is a practice commonly seen in enterprise scenarios so they can keep pipeline logic clean and centralise rules.
Instead of a .checkov.yml file there is also an option to run the scans using Bridgecrew/Prisma clouds Azure policy catalog. This catalog is constantly kept upto date but it does require additional setup including creating an account with Prisma and generating an api key. Given that during the build process Checkov updates its internal Azure Checkov ID list I couldn’t really see the advantage of this so stuck with the .checkov.yaml definition file.
Below is an example of a Checkov implementation designed to break the build when high or critical findings are discovered.
# ============================================================
# SECURITY SCANNING STAGE (ALWAYS RUN)
# ============================================================
stages:
- stage: SecurityScanning
displayName: "Security Scanning (IaC + Dependencies)"
condition: always() # <---- ALWAYS runs even on PRs
jobs:
- job: SecurityScan
displayName: "Run Checkov IaC Scan + GHAS Dependency Scan"
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
# 1) Compile Bicep → ARM - required for Checkov to scan the infrastructure as code templates effectively. This step ensures that all Bicep files are converted to ARM JSON, which Checkov can analyze for security issues. By automating this compilation in the pipeline, we ensure that the latest changes in Bicep files are always included in the security scan without manual intervention.
- script: |
echo "Compiling Bicep files to ARM JSON..."
find . -name "*.bicep" -exec sh -c '
for file in "$@"; do
echo "Building $file"
az bicep build --file "$file"
done
' sh {} +
displayName: "Compile Bicep to ARM"
# 2) Install Checkov - always updates the Azure Checkov rule definitions to the latest version on the build agent, ensuring we catch the newest checks without manual maintenance of rule versions.
- script: |
pip install checkov
displayName: "Install Checkov"
# 3) Run Checkov - make sure you have a .checkov.yml file in the repo root with your desired configuration (frameworks, skips, etc.). This keeps the pipeline YAML clean and focused on execution.
- script: |
set -e
mkdir -p security-results
checkov -d . \
-o sarif \
--output-file-path security-results/checkov-results.sarif
displayName: "Checkov IaC Scan"
# 4) GHAS Dependency Scan - nothing to do with Checkov
- task: AdvancedSecurity-Dependency-Scanning@1
displayName: "GHAS Dependency Scanning (Fail on High Severity)"
condition: succeeded()
inputs:
failOnSeverity: 'error'
# Publish to CodeAnalysisLogs artifact (required for outputting sarif results to the *Scans* tab)
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: 'security-results/checkov-results.sarif'
ArtifactName: 'CodeAnalysisLogs'
An example .checkov.yml definition file. In here you tell the Checkov runner what Azure Checkov Id’s to ignore, what frameworks to ignore, any file paths to skip scanning and also to soft fail (i.e. don’t break the build) on any low to medium findings. when you create the .checkov.yml file place it at the directory root and the build pipeline will find it.
# ============================================================
# Checkov Enterprise Configuration for Azure (Bicep + ARM)
# Purpose: Centralized, low-maintenance, future-proof policy
# ============================================================
# ---- Framework Selection -----------------------------------
# Scan ONLY Azure IaC formats: ARM JSON + Bicep
framework:
- arm
- bicep
# ---- Severity Enforcement ----------------------------------
# Fail the pipeline only for HIGH and CRITICAL findings. THIS DOES NOT work in version 3 and beyond of Checkov so commented it out and left it here because its a bit of a gotcha!
# (Checkov’s built-in severity system)
# severity:
# - CRITICAL
# ---- Path Filters -------------------------------------------
# Skip irrelevant folders that generate noise or irrelevant matches.
skip-path:
- .git
- .github
- .azuredevops
- node_modules
- pipelines
- dist
- out
# ---- Skip Specific Checks (Optional) -------------------------
# Only include checks here when needed (false positives,
# unsupported features, intentional design choices).
# This list should remain short and curated.
skip-check:
# Example skips—replace or remove as needed:
- CKV_AZURE_206 # Storage replication not required
- CKV_AZURE_43 # : : Ensure Storage Accounts adhere to the naming rules
# ---- Output Configuration -----------------------------------
# Let the CLI output remain minimal (CI/CD will handle artifacts).
output:
- cli
# ---- Policy Directory for Custom Enterprise Rules -----------
# (Optional) Allows org-specific policy-as-code additions.
# Uncomment if you have custom YAML/Python rules.
# external-checks-dir:
# - .checkov/policies
# ---- Variable Rendering -------------------------------------
# Helps with Bicep/ARM parameterisation cases.
evaluate-variables: true
# ---- Soft Fail for Non-Critical Findings --------------------
# Lower severity issues will not fail the pipeline.
soft-fail: true
Summary
In summary ive discussed DevSecOps governance and the implementation of Github Advanced Security SAST and SCA testing within build pipelines for the purpose of forcing build failures when discrepancies are discovered. I also discussed SAST in the context of application code and Infra as Code pipelines. I hope you find this helpful on your journey towards a shift-left mind set and in my next post ill talk about techniques to actively block builds through Dynamic Analysis Software testing (DAST) scan findings.

Leave a Reply