14.3 CodeQL SAST in Pipelines and Containers
Key Takeaways
- CodeQL extracts a relational database during compilation and then runs queries over it, which is why a build that performs no compilation yields an empty database.
- Taint tracking follows untrusted data from a source through sanitizers to a sink, detecting injection, path traversal and deserialisation flaws.
- In Azure Pipelines the sequence is AdvancedSecurity-Codeql-Init, the build, then AdvancedSecurity-Codeql-Analyze; in GitHub Actions it is codeql-action/init, the build, then codeql-action/analyze.
- Analysing a containerised build requires setting container on the job so the extractor observes the same toolchain, and a full SDK image because the extractor needs glibc.
- SARIF is the interchange format that lets third-party scanners publish into the same code scanning dashboard as CodeQL.
14.3 CodeQL SAST in Pipelines and Containers
Secret scanning finds credentials that are already text. Finding an exploitable code path requires semantic analysis of a compiled program, which is what CodeQL does - and why a containerized build forces the analysis into the same container.
1. CodeQL: Semantic Static Application Security Testing (SAST)
Traditional static code analyzers rely on simple syntax tree parsing or regular expression pattern matching. These tools generate overwhelming numbers of false positives and fail to trace complex data flows across multiple classes, methods, and files.
CodeQL is Microsoft and GitHub's industry-leading semantic code analysis engine. CodeQL treats code as data.
┌──────────────────────┐ ┌─────────────────────────┐ ┌───────────────────────┐
│ Source Code Files │ ───► │ CodeQL Extractor │ ───► │ CodeQL Database │
│ (C#, Java, TS, Py) │ │ (Monitors Compilers/AST)│ │ (Relational DB Schema)│
└──────────────────────┘ └─────────────────────────┘ └───────────┬───────────┘
│
▼
┌──────────────────────┐ ┌─────────────────────────┐ ┌───────────────────────┐
│ SARIF / Alerts Tab │ ◄─── │ CodeQL Taint Queries │ ◄─── │ Open-Source CodeQL │
│ (Pull Request Gates) │ │ (Source ──► Sink Paths) │ │ Query Packs (GitHub) │
└──────────────────────┘ └─────────────────────────┘ └───────────────────────┘
How CodeQL Works: The Extraction Phase
- Extraction: During the build process, CodeQL extracts the codebase into a relational database capturing abstract syntax trees (AST), control flow graphs (CFG), data flow graphs, and lexical tokens.
- Interpreted Languages (JavaScript/TypeScript, Python, Ruby): Extracted directly from raw source files without compilation.
- Compiled Languages (C/C++, C#, Java/Kotlin, Go, Swift): Requires an active build step (
dotnet build,mvn compile,make). CodeQL hooks into the compiler process to extract code structure as it compiles.
- Database Generation: A queryable relational snapshot of the codebase is saved on the build agent.
- Query Execution: CodeQL runs a suite of declarative queries written in the object-oriented QL language against the database.
Taint Tracking: Sources, Sanitizers, and Sinks
The core strength of CodeQL is taint tracking analysis (tracking data flow from untrusted inputs to dangerous execution points):
- Source: Where untrusted data enters the application (e.g., HTTP request body, query parameters, headers, URL route parameters).
- Sanitizer / Guard: Code logic that validates, escapes, or sanitizes the data (e.g., parameterized SQL queries, HTML entity encoders, regex validators).
- Sink: A dangerous execution function where unvalidated data causes security exploits (e.g.,
SqlCommand.CommandText,eval(),Process.Start(),Response.Write()).
If untrusted data flows from a Source to a Sink without passing through a recognized Sanitizer, CodeQL flags a vulnerability path with cryptographic certainty.
Key Vulnerability Classes Detected by CodeQL
- SQL Injection (CWE-89): Untrusted strings concatenated directly into database query commands.
- Cross-Site Scripting / XSS (CWE-79): Unsanitized user inputs reflected back into browser DOM responses.
- Command Injection (CWE-78): User input passed directly to operating system shell processes (
bash -c,cmd.exe). - Path Traversal (CWE-22): Manipulated file paths (
../../etc/passwd) accessing unauthorized file system directories. - Insecure Deserialization (CWE-502): Deserializing untrusted object streams into runtime memory.
2. Implementing CodeQL in CI/CD Pipelines
Candidates must know how to configure CodeQL scanning in both GitHub Actions and Azure Pipelines.
Pattern A: GitHub Actions Workflow (github/codeql-action)
In GitHub, CodeQL is executed using the official github/codeql-action action suite:
# .github/workflows/codeql-analysis.yml
name: 'CodeQL Security Scanning'
on:
push:
branches: [ 'main' ]
pull_request:
branches: [ 'main' ]
schedule:
- cron: '30 1 * * 1' # Weekly scheduled baseline scan
jobs:
analyze:
name: Analyze Codebase
runs-on: 'ubuntu-latest'
permissions:
actions: read
contents: read
security-events: write # Required to upload SARIF results to Security tab
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript-typescript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
# 1. Initialize CodeQL database for target languages
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-extended # Ingests standard + aggressive security queries
# 2. Build compiled languages (Required for C#, Java, C++, Go)
- name: Build C# Solution
if: matrix.language == 'csharp'
run: |
dotnet restore ContosoApp.sln
dotnet build ContosoApp.sln --configuration Release --no-incremental
# 3. Perform CodeQL Analysis
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
Pattern B: Azure Pipelines Workflow (AdvancedSecurity-* Tasks)
When using GitHub Advanced Security for Azure DevOps, Microsoft provides dedicated pipeline tasks:
# azure-pipelines.yml: Advanced Security in Azure Pipelines
trigger:
- main
pr:
- main
pool:
vmImage: 'windows-latest'
steps:
- checkout: self
fetchDepth: 0 # Deep clone ensures accurate git blame & commit tracking
# 1. Initialize CodeQL environment
- task: AdvancedSecurity-Codeql-Init@1
displayName: 'Initialize CodeQL Analysis Engine'
inputs:
languages: 'csharp'
querysuite: 'security-extended'
# 2. Compile application (CRITICAL: Must occur between Init and Analyze!)
- task: DotNetCoreCLI@2
displayName: 'Compile C# Application'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration Release'
# 3. Analyze CodeQL Database
- task: AdvancedSecurity-Codeql-Analyze@1
displayName: 'Execute CodeQL Taint Analysis'
# 4. Consolidate and Publish Alerts
- task: AdvancedSecurity-Publish@1
displayName: 'Publish Security Alerts to Azure DevOps Repos'
condition: succeededOrFailed()
[!IMPORTANT] Top AZ-400 Exam Concept: The Compiled Language Build Rule A major exam trap involves pipelines that initialize CodeQL (
AdvancedSecurity-Codeql-Init@1) and immediately execute analysis (AdvancedSecurity-Codeql-Analyze@1) without compiling the code. For compiled languages (C#, C++, Java, Go), if the code is not compiled between theInitandAnalyzetasks, the CodeQL database remains empty. The scan will complete with zero findings even if the code contains blatant vulnerabilities!
3. SARIF and Centralized Security Dashboards
SARIF (Static Analysis Results Interchange Format) is an open OASIS standard JSON-based format for exchanging static analysis results between security scanning tools and engineering platforms.
SARIF Architecture and Integration
- Both CodeQL in GitHub Actions and Advanced Security in Azure Pipelines output results natively as
.sariffiles. - Third-party SAST tools (such as SonarQube, Checkmarx, Snyk, and Veracode) can export findings to SARIF.
- Using the
github/codeql-action/upload-sarifaction or Azure Pipelines SARIF upload tasks, third-party scanner findings are uploaded directly into GitHub Security or Azure DevOps Advanced Security dashboards. - Developers view annotations directly within Pull Request code diffs, preventing PR merges when security policies fail.
SAST vs. DAST vs. IAST vs. SCA Comparison Matrix
| Security Discipline | Tool Example | Pipeline Phase | Input Analyzed | Typical Vulnerabilities Detected |
|---|---|---|---|---|
| SAST (Static Application Security Testing) | CodeQL, SonarQube | PR Validation / CI Build | Source code / Abstract Syntax Trees | SQLi, XSS, Buffer Overflows, Path Traversal |
| Secret Scanning | GHAS Secret Scanning, Gitleaks | Pre-commit / Push / CI | Git commits / Text strings | Hardcoded passwords, Azure keys, AWS secrets, PATs |
| SCA (Software Composition Analysis) | Dependabot, Snyk, Black Duck | PR Validation / Scheduled | Package manifests (package.json, pom.xml) | Known CVEs in open-source dependencies, license compliance |
| DAST (Dynamic Application Security Testing) | OWASP ZAP, Burp Suite | Post-Deploy / Staging Gate | Running HTTP web endpoints | Broken authentication, CORS misconfigurations, server headers |
| IAST (Interactive Application Security Testing) | Contrast Security | Integration Testing | Runtime bytecode + agent telemetry | Real-time code execution flaws during test automation |
4. Running CodeQL Analysis in a Container
CodeQL builds its database by observing a real build, so a project that only compiles inside a container must run the analysis inside that same container. Otherwise the autobuild step fails, or worse, silently produces an empty database and a green "no alerts" result.
GitHub Actions. Set container: on the job. Every step - including the CodeQL init, build and analyze steps - then executes inside that image, so the extractor traces the same compiler and toolchain the product uses:
jobs:
analyze:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:9.0
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: csharp
build-mode: manual
- run: dotnet build --no-incremental
- uses: github/codeql-action/analyze@v3
Three constraints decide whether the container job works:
- The image must contain the build toolchain and a compatible glibc. CodeQL's extractor binaries are dynamically linked; minimal images such as
alpine(musl libc) or distroless images cannot host the extractor. Use a full SDK image for analysis and keep the small image for the runtime stage. --no-incremental(or the language equivalent) is mandatory. A cached or up-to-date build performs no compilation, the extractor sees no source, and the resulting database is empty.security-events: writemust be granted, otherwise the analyze step cannot upload the SARIF results to code scanning.
Azure Pipelines. The equivalent is a container job wrapping the Advanced Security tasks:
- job: codeql
container: mcr.microsoft.com/dotnet/sdk:9.0
steps:
- task: AdvancedSecurity-Codeql-Init@1
inputs: { languages: 'csharp' }
- script: dotnet build --no-incremental
- task: AdvancedSecurity-Codeql-Analyze@1
Note the distinction the exam likes to test: running CodeQL in a container analyses the source code that is built inside that image, whereas container image scanning (Defender for Containers, ACR scanning) inspects the published image layers for vulnerable OS and library packages. They answer different questions and neither replaces the other.
5. Realistic Exam Scenario & Common Traps
Scenario: Global FinTech Payment Gateway Pipeline
Organization: Contoso Payments develops an enterprise payment gateway in C# .NET 8 and React hosted in Azure Repos. Developers frequently interact with Stripe API keys and Azure Cosmos DB master keys.
DevOps Strategy Implemented:
- Pre-Receive Defense: The security team enables GHAS for Azure DevOps across all payment repositories and turns on Push Protection. A junior developer attempts to push code containing a live Azure Cosmos DB primary connection string; the
git pushis blocked instantly at the CLI with an explanatory rejection message. - Pull Request Security Gate: A PR validation pipeline runs
AdvancedSecurity-Codeql-Init@1, compiles the solution withDotNetCoreCLI@2, and runsAdvancedSecurity-Codeql-Analyze@1using thesecurity-extendedquery suite. - SARIF Pull Request Annotation: A developer introduces a raw SQL query concatenation (
SELECT * FROM Accounts WHERE Id = '" + id + "'). CodeQL detects the taint flow from the API controller to the database query and automatically flags the exact line in the PR review window, blocking the PR merge branch policy until the query is refactored to use parameterized commands.
Common Exam Traps to Avoid
- Trap: Believing Secret Scanning Push Protection can be bypassed silently. Developers can only bypass push protection if the organization permits it, and every bypass requires choosing a specific justification (false positive, test secret) which is immediately recorded in the security audit log.
- Trap: Assuming CodeQL executes the application binary. CodeQL is strictly a static analysis tool. It analyzes the structure, flow, and semantics of the code; it does not launch web servers or execute HTTP requests (which is the domain of DAST tools like OWASP ZAP).
- Trap: Forgetting the
security-events: writepermission in GitHub Actions. When authoring custom GitHub Actions workflows that execute CodeQL or upload SARIF files, omittingsecurity-events: writefrom the workflow job permissions causes the SARIF upload to fail with an HTTP 403 Forbidden error.
A DevOps engineer is configuring a continuous integration pipeline in Azure Pipelines to run CodeQL static analysis on a compiled C# web application. The pipeline includes the AdvancedSecurity-Codeql-Init@1 task specifying the csharp language, followed immediately by the AdvancedSecurity-Codeql-Analyze@1 task and AdvancedSecurity-Publish@1. When the pipeline runs, CodeQL executes without error but reports zero alerts, despite the presence of multiple known SQL injection vulnerabilities in the source code. What is the cause of this behavior?
A security architect is establishing an enterprise-wide DevSecOps governance model across multiple development teams. The teams use different commercial and open-source static application security testing (SAST) and container analysis tools. The architect wants to standardize how these diverse security tools export their vulnerability findings so they can be ingested directly into the repository security alert dashboard in GitHub and Azure DevOps. Which standardized format should be mandated?
A C# service compiles only inside a .NET SDK container image. A GitHub Actions CodeQL workflow sets 'container:' on the analysis job but the analyze step reports that the database contains no source code. Which two conditions most likely caused the empty database?