Skip to content

Pipeline Stages

All 16 DevSecOps stages are defined in DevSecOps/templates/security-pipeline-template.yml. This page describes each stage in detail: the tool used, its purpose, and the conditions that block the pipeline.


Stage Summary Table

StageNameToolPurposeBlocks on
1Secret ScanGitleaksDetect committed secretsAny secret found
2SASTSonarCloudStatic code analysisQuality gate fail
3Dependency CVEOWASP DCKnown CVE in packagesCritical / High CVE (CVSS ≥ 7)
4Dependency VulnTrivy (deps)Vulnerability scan on lockfilesCritical
5IaC ScanTrivy (IaC)Bicep / YAML misconfigurationHigh
6Dockerfile LintHadolintDockerfile best-practice checkError level
7Build + Unit Tests.NET / Node / FlutterCompile and run unit testsAny test failure
8Docker BuildDockerContainer image buildBuild failure
9Image ScanTrivy (image)Scan built container imageCritical CVE
10SBOMSyftGenerate software bill of materialsNone (informational)
11Integration TestsNewman / REST clientEnd-to-end API test suiteAny test failure
12AI Code ReviewAzure OpenAI GPT-4oAutomated code suggestionsNone (advisory)
13DASTOWASP ZAPDynamic attack surface testHigh findings
14ReportHTML/PDF generatorPublish security reportN/A
15NotifyTeams webhookAlert team on resultsN/A
16ArchiveAzure Blob StorageStore artifacts and reportsN/A

Stage 1 — Secret Scanning (Gitleaks)

Tool: GitleaksWhen: First stage; runs before any build activity.

Gitleaks scans the entire git history of the repository for secrets: API keys, passwords, connection strings, tokens, certificates, and Microtec-specific patterns (e.g., XApiKey).

yaml
- task: Bash@3
  displayName: 'Stage 1 - Secret Scan (Gitleaks)'
  inputs:
    script: |
      docker run --rm \
        -v $(Build.SourcesDirectory):/repo \
        zricethezav/gitleaks:latest \
        detect --source /repo --verbose \
        --config /repo/.gitleaks.toml \
        --redact

Blocks on: Any secret detected. Exit code non-zero fails the stage.

See gitleaks.md for configuration details.


Stage 2 — SAST (SonarCloud)

Tool: SonarCloudWhen: After secret scan, before build.

SonarCloud performs static application security testing across C#, TypeScript, Java, Python, and Dart. It evaluates code quality gates covering coverage, duplication, security hotspots, and code smells.

yaml
- task: SonarCloudPrepare@1
  displayName: 'Stage 2 - SAST Prepare (SonarCloud)'
  inputs:
    SonarCloud: 'SonarCloud-ServiceConnection'
    organization: 'microtec'
    scannerMode: 'MSBuild'    # or CLI for non-.NET
    projectKey: '$(sonarProjectKey)'
    extraProperties: |
      sonar.exclusions=**/obj/**,**/bin/**,**/*.spec.ts
      sonar.coverage.exclusions=**/*Tests*/**

- task: SonarCloudAnalyze@1
  displayName: 'Stage 2 - SAST Analyze'

- task: SonarCloudPublish@1
  displayName: 'Stage 2 - SAST Publish'
  inputs:
    pollingTimeoutSec: '300'

Blocks on: Quality gate FAILED status returned from SonarCloud API.

See sonarcloud.md for quality gate configuration.


Stage 3 — Dependency CVE Check (OWASP Dependency-Check)

Tool: OWASP Dependency-CheckWhen: After SAST.

OWASP DC downloads the NVD (National Vulnerability Database) and matches all project dependencies against known CVEs. Covers NuGet, npm, and Maven package graphs.

yaml
- task: dependency-check-build-task@6
  displayName: 'Stage 3 - Dependency CVE (OWASP DC)'
  inputs:
    projectName: '$(Build.Repository.Name)'
    scanPath: '$(Build.SourcesDirectory)'
    format: 'HTML,JSON'
    failOnCVSS: '7'           # Block on High (7) and above
    suppressionPath: '$(Build.SourcesDirectory)/.owasp-suppression.xml'

Blocks on: Any dependency with CVSS score ≥ 7 (High or Critical).

Tip: False positives can be suppressed in .owasp-suppression.xml with justification and expiry date.


Stage 4 — Dependency Vulnerabilities (Trivy deps)

Tool: Trivy — filesystem mode When: Parallel with Stage 3 (if pipeline allows parallelism).

Trivy scans dependency lockfiles (packages.lock.json, package-lock.json, pubspec.lock) directly without downloading the NVD. Faster than OWASP DC and useful as a second opinion.

yaml
- task: Bash@3
  displayName: 'Stage 4 - Trivy Dependency Scan'
  inputs:
    script: |
      trivy fs \
        --scanners vuln \
        --severity CRITICAL \
        --exit-code 1 \
        --format table \
        $(Build.SourcesDirectory)

Blocks on: Critical severity vulnerabilities only.


Stage 5 — IaC Scanning (Trivy IaC)

Tool: Trivy — config mode When: After dependency scans.

Scans Bicep templates and Kubernetes/Docker Compose YAML files for misconfigurations: open ports, missing resource limits, insecure defaults.

yaml
- task: Bash@3
  displayName: 'Stage 5 - IaC Scan (Trivy)'
  inputs:
    script: |
      trivy config \
        --severity HIGH,CRITICAL \
        --exit-code 1 \
        --format table \
        $(Build.SourcesDirectory)/Devops/azure/infrastructure

Blocks on: High or Critical misconfigurations in Bicep / YAML.

See trivy.md for .trivyignore usage.


Stage 6 — Dockerfile Linting (Hadolint)

Tool: HadolintWhen: Before Docker build.

Hadolint validates Dockerfiles against best practices: COPY vs ADD, apt-get patterns, USER declarations, and security rules (e.g., avoid latest tag, avoid --no-check-certificate).

yaml
- task: Bash@3
  displayName: 'Stage 6 - Dockerfile Lint (Hadolint)'
  inputs:
    script: |
      hadolint \
        --failure-threshold error \
        --config .hadolint.yaml \
        $(dockerfilePath)

Blocks on: Any finding at error severity or above.


Stage 7 — Build + Unit Tests

Tool: dotnet test, npm test, flutter testWhen: After all pre-build scans pass.

Compiles the application and runs the full unit test suite. Test results are published as JUnit XML artifacts.

yaml
# .NET example
- task: DotNetCoreCLI@2
  displayName: 'Stage 7 - .NET Test'
  inputs:
    command: 'test'
    projects: '$(testProject)'
    arguments: >
      --configuration Release
      --collect:"XPlat Code Coverage"
      --results-directory $(Agent.TempDirectory)/TestResults
    publishTestResults: true

Blocks on: Any unit test failure; code coverage below the SonarCloud quality gate threshold.


Stage 8 — Docker Build

Tool: Docker When: After tests pass.

Builds the container image using the service Dockerfile. Uses --mount=type=secret,id=nuget_pat for NuGet authentication (see NuGet Setup docs).

yaml
- task: Docker@2
  displayName: 'Stage 8 - Docker Build'
  inputs:
    containerRegistry: '$(acrServiceConnection)'
    repository: '$(imageName)'
    command: 'build'
    Dockerfile: '$(dockerfilePath)'
    buildContext: '$(Build.SourcesDirectory)'
    tags: '$(Build.BuildId)'
    arguments: '--secret id=nuget_pat,src=$(NUGET_PAT_FILE)'

Blocks on: Docker build exit code non-zero.


Stage 9 — Container Image Scan (Trivy image)

Tool: Trivy — image mode When: Immediately after Docker build; before push.

Scans the freshly built image for OS-level CVEs and application-layer vulnerabilities in the base image layers.

yaml
- task: Bash@3
  displayName: 'Stage 9 - Image Scan (Trivy)'
  inputs:
    script: |
      trivy image \
        --severity CRITICAL \
        --exit-code 1 \
        --ignore-unfixed \
        --format table \
        $(imageName):$(Build.BuildId)

Blocks on: Critical CVEs in the container image.


Stage 10 — SBOM Generation (Syft)

Tool: SyftWhen: After image scan.

Generates a Software Bill of Materials in SPDX and CycloneDX formats. The SBOM is published as a pipeline artifact and archived in the security storage account. This stage is informational and never blocks the pipeline.

yaml
- task: Bash@3
  displayName: 'Stage 10 - SBOM (Syft)'
  inputs:
    script: |
      syft $(imageName):$(Build.BuildId) \
        -o spdx-json=$(Build.ArtifactStagingDirectory)/sbom.spdx.json \
        -o cyclonedx-json=$(Build.ArtifactStagingDirectory)/sbom.cyclonedx.json

Blocks on: Nothing. Always informational.


Stage 11 — Integration Tests

Tool: Newman (Postman CLI) or REST Assured When: After image is pushed to ACR and deployed to the ephemeral dev environment.

Runs the full API test collection against the deployed service. Covers happy-path, error-path, authentication, and authorization scenarios.

yaml
- task: Bash@3
  displayName: 'Stage 11 - Integration Tests (Newman)'
  inputs:
    script: |
      newman run $(Build.SourcesDirectory)/tests/postman-collection.json \
        --environment $(Build.SourcesDirectory)/tests/env-dev.json \
        --reporters cli,junit \
        --reporter-junit-export $(Agent.TempDirectory)/integration-results.xml

Blocks on: Any test failure in the collection.


Stage 12 — AI Code Review (Azure OpenAI)

Tool: Azure OpenAI GPT-4o When: After integration tests.

The pipeline sends the diff (git diff origin/main...HEAD) to Azure OpenAI for a security-focused code review. The response is published as a PR comment and as a pipeline artifact. This stage is advisory only and does not block the pipeline.

yaml
- task: Bash@3
  displayName: 'Stage 12 - AI Code Review'
  inputs:
    script: |
      DIFF=$(git diff origin/main...HEAD -- '*.cs' '*.ts' '*.dart')
      REVIEW=$(python scripts/ai-review.py "$DIFF")
      echo "##vso[task.setvariable variable=aiReview]$REVIEW"

Blocks on: Nothing. Advisory output only.


Stage 13 — DAST (OWASP ZAP)

Tool: OWASP ZAPWhen: Against the deployed dev environment after integration tests pass.

ZAP performs an active scan against the running application: authentication bypass attempts, injection attacks, XSS probes, CSRF checks, and API fuzzing.

yaml
- task: Bash@3
  displayName: 'Stage 13 - DAST (OWASP ZAP)'
  inputs:
    script: |
      docker run --rm \
        -v $(Agent.TempDirectory)/zap:/zap/wrk \
        ghcr.io/zaproxy/zaproxy:stable \
        zap-api-scan.py \
          -t $(appBaseUrl)/swagger/v1/swagger.json \
          -f openapi \
          -r zap-report.html \
          -x zap-report.xml \
          -z "-config replacer.full_list(0).description=auth \
              -config replacer.full_list(0).enabled=true \
              -config replacer.full_list(0).matchtype=REQ_HEADER \
              -config replacer.full_list(0).matchstr=Authorization \
              -config replacer.full_list(0).replacement=Bearer\ $(testJwt)"
      
      # Fail on high-risk findings (riskCode 3)
      python scripts/zap-check.py --report zap-report.xml --max-risk 3

Blocks on: Any finding with risk code 3 (High) or above.


Stage 14 — Security Report

Tool: Custom HTML/PDF generator (Python + WeasyPrint) When: After DAST, regardless of previous stage outcomes.

Aggregates results from all previous stages into a single HTML security report and converts it to PDF. Published as a pipeline artifact and linked in the PR description.

Blocks on: Nothing (runs in always() condition).


Stage 15 — Notifications

Tool: Azure DevOps Teams webhook / service hook When: After report generation.

Sends a summary card to the designated Microsoft Teams channel:

  • Pass/fail status per stage
  • Links to detailed reports
  • Assignee for any blocking findings

Blocks on: Nothing.


Stage 16 — Archive

Tool: Azure Blob Storage (az storage blob upload-batch) When: Last stage, always runs.

Uploads all security artifacts to the long-term security archive storage account:

  • SBOM files (SPDX + CycloneDX)
  • ZAP HTML and XML reports
  • OWASP DC HTML and JSON reports
  • SonarCloud quality gate JSON
  • AI review output

Artifacts are stored under {year}/{month}/{repoName}/{buildId}/.

Blocks on: Nothing.


Stage Dependencies (Simplified DAG)

1 (Gitleaks)
    └── 2 (SonarCloud)
    └── 3 (OWASP DC) ─┐
    └── 4 (Trivy deps) ┤
    └── 5 (Trivy IaC)  ┤
    └── 6 (Hadolint) ──┘
                        └── 7 (Build + Tests)
                                └── 8 (Docker build)
                                        └── 9 (Trivy image)
                                                └── 10 (SBOM)
                                                └── 11 (Integration tests)
                                                        └── 12 (AI review)
                                                        └── 13 (ZAP)
                                                                └── 14 (Report)
                                                                └── 15 (Notify)
                                                                └── 16 (Archive)

Internal Documentation — Microtec Platform Team