SBOM & supply-chain transparency
This page provides: complete syft commands for SBOM generation (CycloneDX JSON and SPDX formats), the syft + grype vulnerability scanning command chain, a minimal SPDX JSON format example, GitHub Actions integration for SBOM generation and storage, and 3 typical supply chain attack patterns with corresponding defenses.
SBOMs are valuable when they are diffable and automatable: the same coordinate should mean the same logical component across tools. The SKILL should document generation commands, schema versions, merge rules for container layers and transitive deps, plus artifact retention and ACLs.
syft SBOM generation commands and grype vulnerability scan
syft: generate SBOM in CycloneDX JSON and SPDX formats
# Install syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate CycloneDX JSON SBOM from current directory
syft dir:. \
--output cyclonedx-json=sbom-cyclonedx.json \
--name myapp \
--version $(git rev-parse --short HEAD)
# Generate SPDX JSON format
syft dir:. \
--output spdx-json=sbom-spdx.json
# Generate from container image (includes base layers)
syft myapp:latest \
--output cyclonedx-json=sbom-image.json \
--scope all-layers
# syft + grype: complete vulnerability scan command chain
syft myapp:latest -o json > /tmp/sbom.json
grype sbom:/tmp/sbom.json \
--fail-on high \
--output json \
> grype-report.json
# Show Critical/High vulnerability summary
grype sbom:/tmp/sbom.json \
--fail-on high \
--output table | head -40
- Ship checksums and optional signatures; call out internal forks and patched versions with provenance.
- Link to dependency upgrades and CVE tickets in a discover → assess → fix → rescan loop.
- For regulatory or customer audits, SBOMs should cross-check with build provenance (SLSA, in-toto) down to commit and pipeline run IDs.
SPDX minimal format example and GitHub Actions integration
SPDX JSON minimal example (showing required fields)
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "myapp-sbom",
"documentNamespace": "https://example.com/sbom/myapp-abc1234",
"documentDescribes": ["SPDXRef-Package-myapp"],
"packages": [
{
"SPDXID": "SPDXRef-Package-myapp",
"name": "myapp",
"versionInfo": "1.2.3-abc1234",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"licenseDeclared": "MIT",
"licenseConcluded": "MIT",
"copyrightText": "Copyright 2025 Example Corp"
},
{
"SPDXID": "SPDXRef-Package-express-4.18.2",
"name": "express",
"versionInfo": "4.18.2",
"downloadLocation": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"filesAnalyzed": false,
"licenseDeclared": "MIT",
"licenseConcluded": "MIT",
"externalRefs": [
{"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:npm/express@4.18.2"}
]
}
],
"relationships": [
{"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-Package-myapp"},
{"spdxElementId": "SPDXRef-Package-myapp",
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-Package-express-4.18.2"}
]
}
Integrating SBOM generation and storage in GitHub Actions
# .github/workflows/sbom.yml
name: SBOM Generation
on:
push:
branches: ["main"]
release:
types: [published]
jobs:
sbom:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # for cosign OIDC signing
steps:
- uses: actions/checkout@v4
- name: Generate SBOM with syft
uses: anchore/sbom-action@v0
with:
path: .
format: spdx-json
output-file: sbom.spdx.json
artifact-name: sbom-${{ github.sha }}.spdx.json
- name: Scan SBOM with grype
uses: anchore/scan-action@v3
with:
sbom: sbom.spdx.json
fail-build: true
severity-cutoff: high
- name: Upload SBOM as release asset
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: sbom.spdx.json
asset_name: sbom-${{ github.event.release.tag_name }}.spdx.json
asset_content_type: application/json
3 typical supply chain attack patterns and defenses
3 typical supply chain attack patterns and corresponding defenses:
-
① Dependency Confusion: An attacker publishes a malicious package with the same name as an internal private package on a public registry; npm/pip resolves to the public version first.
Defense: Use a private registry and pin scope mappings in.npmrc/pip.conf; enforce integrity hashes for all packages (lockfile); verify in SBOM that PURLs come from the expected registry. -
② Build System Compromise (SolarWinds-style): Attackers infiltrate the CI/CD system and inject malicious code into build artifacts without touching the source repo.
Defense: Implement SLSA Level 3 provenance (builds run in a controlled environment with signed attestations); SBOM must include the build environment hash bound to the image digest; use Sigstore/cosign for signing. -
③ Malicious Maintainer (XZ Utils-style): An attacker participates in a legitimate open-source project over time, gains maintainer access, and injects a backdoor.
Defense: Pin dependencies to exact commit hashes (not version tags); regularly diff changelogs of critical dependencies; use Renovate/Dependabot auto-PRs with SBOM diff review; trigger automated vulnerability scans on upstream library changes.
# .npmrc — prevent Dependency Confusion
@mycompany:registry=https://npm.mycompany.internal
registry=https://registry.npmjs.org
# Pin dependency to git commit hash (package.json)
"critical-lib": "github:org/critical-lib#a1b2c3d4e5f6..."
# cosign: sign image and attach SBOM attestation
cosign sign --key cosign.key myapp:latest
cosign attest --predicate sbom.spdx.json \
--type spdxjson \
myapp:latestSBOM lifecycle flow
Follow a fixed order so vulnerability comparison does not run before formats and versions are locked—otherwise fields go missing or coordinates fail to match.
[ Lock: SPDX / CycloneDX + schema + generator version ]
│
▼
[ CI: lockfiles / image layers / transitive deps → SBOM fragments ]
│
▼
[ Merge: dedupe key (PURL@version), layer order, patch notes ]
│
▼
[ Sign & store: checksum, registry ACL, bind to image digest ]
│
▼
[ Consume: CVE/VEX scans, license reports, customer/regulator deliverables ]
│
▼
[ Version diff: new components / license shifts / vuln delta summary ]
Package URL builder
Package URL (PURL) is a cross-ecosystem coordinate: pkg:type/namespace/name@version. The tool below encodes common types (simplified—see the spec for complex qualifiers) so you can paste consistent coordinates into SKILLs or tickets.
SKILL skeleton
---
name: sbom
description: Generate SPDX/CycloneDX SBOM with syft+grype vulnerability scanning, sign and store, check supply chain attacks
---
# Steps
1. Choose format: SPDX (license compliance) or CycloneDX (vulnerability VEX); pin schema and tool versions
2. Run syft to generate SBOM: dir:. or image, specify --name and --version
3. Run grype scan: grype sbom:/tmp/sbom.json --fail-on high
4. GitHub Actions: sbom-action to generate + scan-action to check + upload as release asset
5. SBOM field validation: spdxVersion, SPDXID, documentNamespace, purl must not be empty
6. Bind to image digest: cosign attest --predicate sbom.json --type spdxjson
7. Dependency Confusion defense: npmrc/pip.conf pin scope, lockfile with integrity hashes
8. Build system compromise defense: SLSA Level 3, cosign signing, provenance attestation
9. Malicious maintainer defense: pin to git commit hash; regularly diff critical dependencies
10. SBOM version diff: compare new components, license changes, and vuln surface delta vs previous version
11. Artifact retention: SBOM stored alongside image with product/version/arch/build-id lookup keys
12. Consumption: CVE/VEX scans, license reports, customer/regulatory deliverables
# Anti-patterns
- Do NOT manually maintain a dependency list (must auto-generate from lockfile)
- Do NOT use custom non-standard SPDX IDs in SBOM