DevSecOps · Project case study

Secure CI/CD on Azure

A GitHub Actions workflow that checks a containerized web app for secrets, unsafe code, and vulnerable dependencies before it can be built and deployed.

4 parallel security checksCycloneDX SBOMDocker → Azure App Service
View source on GitHub
Successful GitHub Actions run showing four security checks, SBOM generation, image build, and Azure deployment
An actual successful run: all four checks passed before SBOM generation, image publishing, and deployment.

The problem

A successful build says little about whether a release is safe. Credentials can enter source control, dependencies can contain known vulnerabilities, and insecure patterns can pass code review. Without a component inventory, investigating a later supply-chain issue becomes harder.

The solution

I put security ahead of the build. Four checks run concurrently; a blocking finding stops the downstream jobs. Successful runs produce an SBOM, publish a Docker image, and deploy it to Azure.

  1. 01

    Security gate

    Gitleaks, TruffleHog, Semgrep, and Trivy run in parallel on pushes and pull requests.

  2. 02

    SBOM

    Once checks pass, Syft creates a CycloneDX inventory and saves it as an artifact and release asset.

  3. 03

    Build & push

    On the main branch, Docker builds the Next.js app and pushes the image to Azure Container Registry.

  4. 04

    Deploy

    Azure App Service receives the image only after the preceding jobs succeed.

Pipeline architecture

From pull request to security evidence, container registry, and Azure App Service.

Open full-size diagram
Four-stage pipeline architecture: parallel security scans, Syft SBOM, Docker image in Azure Container Registry, and Azure App Service deployment

Live test results

The workflow was tested with real credential states, a vulnerable package, and a clean release. “Fail” means a check raised a blocking finding; “Pass” means it did not.

ScenarioGitleaksTruffleHogSemgrepTrivyWhat happened
#20Example AWS keyPassPassPassPassFake, low-entropy key ignored.
#22Real IPinfo token in URLPassPassPassPassNo distinctive token pattern matched; the scans passed.
#23Revoked GitHub tokenFailPassFailPassPattern flagged; live verification confirmed revocation.
#24Active GitHub tokenFailFailFailPassVerified live credential blocked the pipeline.
#25Unchanged token + vulnerable lodashPassPassFailFailFull SAST scan and dependency scan caught issues.
#27Clean production runPassPassPassPassAll gates passed; the app deployed to Azure.

Why the IPinfo test passed: the token was real, but unlike a GitHub personal access token, its string had no distinctive prefix or fixed format that matched the configured detectors. This exposed a coverage gap in pattern-based scanning: the gate can only block secrets its checks recognize.

A traceable software inventory

After the security jobs pass, Syft generates a CycloneDX JSON SBOM. The workflow stores it as a run artifact and attaches it to a GitHub release, so the dependency inventory stays associated with the shipped version.

The scans also leave SARIF reports or logs for investigation. This creates useful evidence of what ran and what it found, without claiming the checks eliminate every risk.

Why the SBOM matters in Europe

The EU Cyber Resilience Act requires manufacturers of covered products with digital elements to document components, including a machine-readable SBOM covering at least top-level dependencies. Its main requirements apply from 11 December 2027. This pipeline demonstrates automated inventory generation; an SBOM alone does not establish CRA compliance. Read the regulation ↗

GitHub release v1.0.1 with the generated sbom.cdx.json attached as a release asset
The generated sbom.cdx.json attached to a release.

Built with

Next.js · GitHub Actions · Gitleaks · TruffleHog · Semgrep · Trivy · Syft · CycloneDX · Docker · Azure Container Registry · Azure App Service