Learn to scan Azure resources for cost governance violations using PSRule, Checkov, Cloud Custodian, and Infracost.
View the Project on GitHub devopsabcs-engineering/finops-scan-workshop
| Duration | 30 minutes |
| Level | Intermediate |
| Prerequisites | Lab 02, Lab 03, Lab 04, or Lab 05 (at least one) |
By the end of this lab, you will be able to:
You will examine the SARIF v2.1.0 format that all four scanner tools produce.
Open any SARIF file you generated in a previous lab (for example, reports/psrule-001.sarif or reports/custodian.sarif).
Review the top-level structure. Every SARIF file follows this schema:
{
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"runs": [
{
"tool": {
"driver": {
"name": "CloudCustodian",
"version": "1.0.0",
"informationUri": "https://cloudcustodian.io",
"rules": [
{
"id": "check-required-tags",
"name": "check-required-tags",
"shortDescription": {
"text": "Cloud Custodian policy violation"
},
"defaultConfiguration": {
"level": "warning"
}
}
]
}
},
"results": [
{
"ruleId": "check-required-tags",
"level": "warning",
"message": {
"text": "Resource rg-finops-demo-001 violates policy check-required-tags"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "infra/main.bicep"
},
"region": {
"startLine": 1
}
}
}
]
}
]
}
]
}
Understand the four main sections:
| Section | Purpose |
|---|---|
version / $schema |
Declares SARIF v2.1.0 compliance |
runs[].tool.driver |
Identifies the scanner tool, version, and rule definitions |
runs[].tool.driver.rules[] |
Defines rule IDs, descriptions, severity, and help URLs |
runs[].results[] |
Contains individual findings with rule ID, severity, message, and location |
Note how physicalLocation ties a finding to a specific file and line number. GitHub Code Scanning uses this to annotate source files in pull requests.

[!TIP] SARIF (Static Analysis Results Interchange Format) is an OASIS standard. GitHub, Azure DevOps, and many IDE extensions can consume SARIF files. By producing SARIF from all 4 tools, you get a unified view of FinOps violations across your entire scanning platform.
You will upload a SARIF file to the GitHub Code Scanning API using the GitHub CLI.
Choose a SARIF file from a previous lab. This example uses the PSRule output for app 001:
SARIF_FILE="reports/psrule-001.sarif"
Compress and base64-encode the SARIF file (required by the API):
cat $SARIF_FILE | gzip | base64 > /tmp/sarif-base64.txt
On Windows PowerShell:
$bytes = [System.IO.File]::ReadAllBytes("reports/psrule-001.sarif")
$ms = New-Object System.IO.MemoryStream
$gz = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Compress)
$gz.Write($bytes, 0, $bytes.Length)
$gz.Close()
$encoded = [Convert]::ToBase64String($ms.ToArray())
$encoded | Out-File /tmp/sarif-base64.txt -NoNewline
Upload to the Code Scanning endpoint:
gh api -X POST /repos/{owner}/{repo}/code-scanning/sarifs \
-f "commit_sha=$(git rev-parse HEAD)" \
-f "ref=refs/heads/main" \
-f "sarif=$(cat /tmp/sarif-base64.txt)" \
-f "tool_name=PSRule"
Replace {owner} and {repo} with your fork’s owner and repository name.
The API returns a response with a url field. You can poll this URL to check the processing status:
gh api /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}
Processing takes a few seconds. Once complete, the findings appear in the Security tab.

[!IMPORTANT] The Code Scanning API requires the repository to have GitHub Advanced Security enabled. If you receive a 403 error, check that GHAS is enabled in your repository settings under Settings → Code security and analysis.
You will navigate to the GitHub Security tab to view the uploaded FinOps alerts.
Open your repository on GitHub.
Click the Security tab in the top navigation bar.
Click Code scanning in the left sidebar.

[!NOTE] If you uploaded SARIF from multiple tools, use the Tool filter to view findings from a specific scanner. This helps during triage when you want to focus on one category of violations at a time.
You will practise the triage workflow for FinOps alerts.
In the Code scanning alerts list, click on any alert.
Dismiss one alert as Used in tests (since these demo apps are intentionally misconfigured).

[!TIP] In a production FinOps programme, assign alert triage to specific team members. Use GitHub Code Scanning API webhooks to create automated notifications when new high-severity FinOps alerts appear.
You will understand how the automated pipeline uploads SARIF to each demo app’s repository.
Open .github/workflows/finops-scan.yml and find the cross-repo-upload job.
Review the job configuration:
cross-repo-upload:
needs: [psrule-scan, checkov-scan, custodian-scan]
if: always() && (needs.psrule-scan.result == 'success' || needs.checkov-scan.result == 'success')
runs-on: ubuntu-latest
strategy:
matrix:
app: ['001', '002', '003', '004', '005']
The job runs after all three scan jobs complete and uses a matrix to process each demo app.
actions/download-artifact@v4 with a pattern matchThe upload script compresses each SARIF file, looks up the latest commit SHA on the target repo’s main branch, and calls the Code Scanning API — the same steps you performed manually in Exercise 6.2:
SARIF_CONTENT=$(gzip -c "$sarif_file" | base64 -w0)
COMMIT_SHA=$(gh api repos/{org}/finops-demo-app-{app}/commits/main --jq '.sha')
gh api --method POST \
repos/{org}/finops-demo-app-{app}/code-scanning/sarifs \
-f "commit_sha=$COMMIT_SHA" \
-f "ref=refs/heads/main" \
-f "sarif=$SARIF_CONTENT"

[!NOTE] The cross-repo upload requires an
ORG_ADMIN_TOKENsecret withsecurity_events: writepermission on all target repositories. Thesetup-oidc.ps1script in Lab 07 does not cover this token — it must be created as a GitHub personal access token or fine-grained token.
Before proceeding, verify:
gh apiProceed to Lab 07 — GitHub Actions Pipelines and Cost Gates.