Appearance
SBOM — Software Bill of Materials (Syft)
A Software Bill of Materials is a complete, machine-readable inventory of every dependency in a container image or build artefact. Microtec generates CycloneDX SBOMs using Syft as part of every container build pipeline.
Why SBOM Matters
Why We Generate SBOMs
- Supply chain transparency — Know exactly which open-source packages ship inside every image.
- Vulnerability triage — When a CVE drops, query the SBOM to identify which services are affected without rescanning all images.
- Compliance — Executive Order 14028 (US) and EU Cyber Resilience Act both mandate machine-readable SBOMs for software suppliers.
- Audit trail — Every production release has a signed SBOM attached as a pipeline artefact, giving auditors a verifiable record.
Tool: Syft
| Property | Value |
|---|---|
| Tool | Syft by Anchore |
| SBOM Format | CycloneDX 1.4 (JSON) |
| Pipeline stage | Stage 9 — SBOM Generation (runs after Trivy image scan) |
| Artefact name | sbom-<service>-<buildId>.cdx.json |
| Retention | 90 days in Azure Pipelines |
Pipeline Integration
yaml
- stage: SBOM
displayName: 'Stage 9 - SBOM Generation (Syft)'
dependsOn: TrivyScan
condition: succeeded()
jobs:
- job: GenerateSBOM
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Bash@3
displayName: 'Install Syft'
inputs:
targetType: inline
script: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh \
| sh -s -- -b /usr/local/bin
- task: Bash@3
displayName: 'Generate CycloneDX SBOM'
inputs:
targetType: inline
script: |
IMAGE="${ACR_NAME}.azurecr.io/$(serviceName):$(Build.BuildId)"
syft "${IMAGE}" \
--output cyclonedx-json \
--file "$(Build.ArtifactStagingDirectory)/sbom-$(serviceName)-$(Build.BuildId).cdx.json"
- task: PublishBuildArtifacts@1
displayName: 'Attach SBOM to pipeline run'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'sbom'
publishLocation: 'Container'CycloneDX Format Overview
CycloneDX JSON produced by Syft contains:
json
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2024-01-15T10:30:00Z",
"component": {
"name": "mic-gateway",
"version": "1.0.0+build.42"
}
},
"components": [
{
"type": "library",
"name": "Microsoft.AspNetCore",
"version": "8.0.1",
"purl": "pkg:nuget/Microsoft.AspNetCore@8.0.1",
"licenses": [{ "license": { "id": "MIT" } }]
}
]
}Key fields:
| Field | Description |
|---|---|
metadata.component | The scanned image / service name |
components[].purl | Package URL — universally unique identifier for the dependency |
components[].licenses | SPDX license identifier |
components[].hashes | SHA-256 hash for integrity verification |
What Syft Scans
Syft catalogues packages from all ecosystems present in the image layers:
| Ecosystem | Source files scanned |
|---|---|
| NuGet (.NET) | *.deps.json, packages.lock.json |
| npm (Angular) | package-lock.json, node_modules/ |
| Dart/Pub (Flutter) | pubspec.lock |
| OS packages | dpkg, rpm, apk databases in base image |
| Python | requirements.txt, site-packages/ |
Syft + Trivy Complement Each Other
Trivy flags vulnerabilities; Syft lists packages. Both run in the pipeline. Trivy is the security gate; Syft is the audit artefact. Use Syft output to answer "what version of package X ships in this image?"
Querying the SBOM
After downloading sbom-<service>-<buildId>.cdx.json from a pipeline run:
bash
# List all NuGet packages
jq '.components[] | select(.purl | startswith("pkg:nuget")) | "\(.name) \(.version)"' sbom.cdx.json
# Find a specific package across all components
jq '.components[] | select(.name == "System.Text.Json")' sbom.cdx.json
# List all licenses in use
jq '[.components[].licenses[]?.license.id] | unique | sort' sbom.cdx.json
# Check if a vulnerable package version is present (e.g. CVE triage)
jq '.components[] | select(.name == "Newtonsoft.Json" and .version == "12.0.3")' sbom.cdx.jsonSBOM in the DevSecOps Pipeline
Known Limitations
Runtime Dependencies Not Captured
Syft scans image layers at build time. Packages loaded at runtime via reflection or dynamic assembly loading (common in .NET plugin architectures) will not appear in the SBOM. Supplement with runtime RASP tooling if full runtime coverage is required.
- SBOM is generated per-service, not per-environment. The same SBOM covers all environments a given image tag is deployed to.
- Syft does not assign CVE scores — use Trivy for vulnerability scoring.