Summary
Invoke-SecurityGate.ps1 reports 0 container vulnerabilities and PASSES even when Invoke-ContainerSecurityScan.ps1 has found vulnerabilities (e.g., 34 including 1 critical during build #4810). The aggregate security gate is completely blind to container scan results.
Root Cause
File naming pattern mismatch between the scanner and the gate:
| Component |
Pattern |
Example |
Invoke-ContainerSecurityScan.ps1 writes |
${ImageName}-${ImageTag}-${timestamp}.json |
mqtt-tools/mqtt-otel-trace-exporter-4810-20250115-143022.json |
Invoke-SecurityGate.ps1 searches for |
grype-*.json |
Never matches scanner output |
The scanner never prefixes output files with grype-, so Get-GrypeScanResult returns an empty array, ContainerFindings = 0, and the gate passes.
Secondary Issue: Forward Slash in ImageName
ApplicationBuilder.Build.psm1 passes ImageName values like mqtt-tools/mqtt-otel-trace-exporter (containing /). When Join-Path computes the output path, this creates a subdirectory under security-reports/:
./security-reports/mqtt-tools/mqtt-otel-trace-exporter-4810-20250115-143022.json
Even if the grype- prefix were corrected, the first glob $ResultsPath/grype-*.json would miss files in subdirectories. The recursive ** pattern may work but is fragile.
Minor Typo
In Invoke-SecurityGateEvaluation, the variable is named $gryteFindings instead of $grypeFindings (cosmetic but contributes to maintenance confusion).
Data Flow
- Pipeline triggers
Application-Builder.ps1 → Phase 7 calls Invoke-SecurityScan
Invoke-ContainerSecurityScan.ps1 runs Grype, writes results as ${ImageName}-${ImageTag}-${timestamp}.json (no grype- prefix)
- Security Gate step (runs with
condition: always()) calls Invoke-SecurityGate.ps1
- Gate searches for
grype-*.json → finds 0 files → reports 0 container vulnerabilities → PASSES
Additional Finding: Dead breakBuild Parameter
application-build-template.yml defines a breakBuild boolean parameter (default: false) but it is never referenced in the template body. Build-breaking is controlled by the hardcoded -ExitOnFailure $true on the Security Gate step. This parameter should either be wired to -ExitOnFailure or removed.
Severity
Critical — The aggregate security gate is completely blind to container vulnerabilities. During build #4810, 34 vulnerabilities (1 critical, 9 high, 21 medium, 3 low) in the mqtt-otel-trace-exporter CBL-Mariner 2.0 base image were invisible to the gate. The per-image scan in Application-Builder correctly failed the build, but the aggregate gate is the intended enforcement point.
Recommended Fix
Option A (Preferred): Fix scanner output naming
In Invoke-ContainerSecurityScan.ps1, Invoke-GrypeScan function:
# Current
$reportBaseName = "${ImageName}-${ImageTag}-${timestamp}"
# Fix: Add grype- prefix and sanitize slashes
$sanitizedImageName = $ImageName -replace '[/\\]', '-'
$reportBaseName = "grype-${sanitizedImageName}-${ImageTag}-${timestamp}"
Option B: Fix gate search pattern (more fragile)
Update Get-GrypeScanResult to search *.json with content-based filtering.
Option C (Recommended): Both changes for defense in depth
Apply Option A and ensure the gate also supports recursive discovery.
Also Fix
- Wire
breakBuild parameter to -ExitOnFailure or remove the dead parameter
- Fix
$gryteFindings typo to $grypeFindings
- Audit
Get-CheckovScanResult and Get-DependencyAuditResult for the same class of naming mismatch bug
Affected Files
scripts/security/Invoke-SecurityGate.ps1 — Get-GrypeScanResult glob pattern
scripts/security/Invoke-ContainerSecurityScan.ps1 — Invoke-GrypeScan output naming
scripts/build/modules/ApplicationBuilder.Build.psm1 — Invoke-SecurityScan passes ImageName with /
.azdo/templates/application-build-template.yml — Dead breakBuild parameter, Security Gate step
Summary
Invoke-SecurityGate.ps1reports 0 container vulnerabilities and PASSES even whenInvoke-ContainerSecurityScan.ps1has found vulnerabilities (e.g., 34 including 1 critical during build #4810). The aggregate security gate is completely blind to container scan results.Root Cause
File naming pattern mismatch between the scanner and the gate:
Invoke-ContainerSecurityScan.ps1writes${ImageName}-${ImageTag}-${timestamp}.jsonmqtt-tools/mqtt-otel-trace-exporter-4810-20250115-143022.jsonInvoke-SecurityGate.ps1searches forgrype-*.jsonThe scanner never prefixes output files with
grype-, soGet-GrypeScanResultreturns an empty array,ContainerFindings = 0, and the gate passes.Secondary Issue: Forward Slash in ImageName
ApplicationBuilder.Build.psm1passesImageNamevalues likemqtt-tools/mqtt-otel-trace-exporter(containing/). WhenJoin-Pathcomputes the output path, this creates a subdirectory undersecurity-reports/:Even if the
grype-prefix were corrected, the first glob$ResultsPath/grype-*.jsonwould miss files in subdirectories. The recursive**pattern may work but is fragile.Minor Typo
In
Invoke-SecurityGateEvaluation, the variable is named$gryteFindingsinstead of$grypeFindings(cosmetic but contributes to maintenance confusion).Data Flow
Application-Builder.ps1→ Phase 7 callsInvoke-SecurityScanInvoke-ContainerSecurityScan.ps1runs Grype, writes results as${ImageName}-${ImageTag}-${timestamp}.json(nogrype-prefix)condition: always()) callsInvoke-SecurityGate.ps1grype-*.json→ finds 0 files → reports 0 container vulnerabilities → PASSESAdditional Finding: Dead
breakBuildParameterapplication-build-template.ymldefines abreakBuildboolean parameter (default:false) but it is never referenced in the template body. Build-breaking is controlled by the hardcoded-ExitOnFailure $trueon the Security Gate step. This parameter should either be wired to-ExitOnFailureor removed.Severity
Critical — The aggregate security gate is completely blind to container vulnerabilities. During build #4810, 34 vulnerabilities (1 critical, 9 high, 21 medium, 3 low) in the mqtt-otel-trace-exporter CBL-Mariner 2.0 base image were invisible to the gate. The per-image scan in Application-Builder correctly failed the build, but the aggregate gate is the intended enforcement point.
Recommended Fix
Option A (Preferred): Fix scanner output naming
In
Invoke-ContainerSecurityScan.ps1,Invoke-GrypeScanfunction:Option B: Fix gate search pattern (more fragile)
Update
Get-GrypeScanResultto search*.jsonwith content-based filtering.Option C (Recommended): Both changes for defense in depth
Apply Option A and ensure the gate also supports recursive discovery.
Also Fix
breakBuildparameter to-ExitOnFailureor remove the dead parameter$gryteFindingstypo to$grypeFindingsGet-CheckovScanResultandGet-DependencyAuditResultfor the same class of naming mismatch bugAffected Files
scripts/security/Invoke-SecurityGate.ps1—Get-GrypeScanResultglob patternscripts/security/Invoke-ContainerSecurityScan.ps1—Invoke-GrypeScanoutput namingscripts/build/modules/ApplicationBuilder.Build.psm1—Invoke-SecurityScanpassesImageNamewith/.azdo/templates/application-build-template.yml— DeadbreakBuildparameter, Security Gate step