</>Học Dev
Bài học

Tuần 3 - Ngày 17: SBOM & Dependency Scanning

Tuần 3 – Ngày 17

Mục tiêu học tập

  • Hiểu SBOM (Software Bill of Materials) là gì, vì sao đang trở thành yêu cầu compliance
  • So sánh hai chuẩn SBOM: SPDX và CycloneDX
  • Biết các công cụ sinh SBOM: syft, cdxgen, npm sbom, Trivy
  • Nắm pipeline dependency scanning: npm/pip-audit, Snyk, Dependabot, Renovate
  • Biết container image scanning: Trivy, Grype, Anchore
  • Hiểu CVE database, CVSS score, và khác biệt giữa severity và exploitability
  • Tích hợp scanning vào CI: fail build trên high-severity CVE, allowlist khi cần

1. SBOM là gì?

SBOM = "Software Bill of Materials"

Tương tự như:
- BOM (Bill of Materials) trong sản xuất ô-tô: danh sách linh kiện
- Nutrition label trên thực phẩm: liệt kê thành phần

SBOM cho phần mềm: liệt kê MỌI component (lib, framework, tool) trong
một artifact (binary, container image, source release), kèm:
- Tên component
- Version
- License
- Supplier
- Hash (cryptographic checksum)
- Quan hệ (cái nào depends on cái nào)

Vì sao SBOM trở thành bắt buộc

2020: SolarWinds attack — backdoor trong Orion software ảnh hưởng 18,000 tổ chức
2021: Log4Shell (CVE-2021-44228) — log4j 2.x có RCE, mọi app Java dùng log4j bị
      Câu hỏi đau: "tôi có dùng log4j không? phiên bản nào? ở đâu?"
      Nếu không có SBOM → phải scan thủ công hàng trăm app trong vài giờ

2021-05: Executive Order 14028 (US) — yêu cầu SBOM cho mọi software bán cho US gov
2023-Q4: EU Cyber Resilience Act (CRA) — yêu cầu SBOM cho hardware/software bán EU
2024:    SBOM bắt đầu trở thành mandatory với critical infra

Use case cụ thể

  1. Incident response: CVE mới công bố → query SBOM database → biết ngay app nào bị ảnh hưởng
  2. License compliance: đảm bảo không vô tình ship code GPL trong sản phẩm proprietary
  3. Supply chain audit: customer hỏi "bạn có dùng dep X không?" → trả lời chính xác
  4. Vulnerability management: continuous scan SBOM với CVE database

2. SPDX vs CycloneDX

SPDX (Software Package Data Exchange)

  • ISO/IEC 5962:2021 — chuẩn quốc tế
  • Maintained bởi Linux Foundation từ 2010
  • Định dạng: tag-value, JSON, YAML, RDF, spreadsheet
  • Mạnh về license metadata (SPDX License List là chuẩn license identifier)
{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "my-app-1.0.0",
  "documentNamespace": "https://example.com/sbom/my-app-1.0.0",
  "creationInfo": {
    "created": "2025-06-15T10:00:00Z",
    "creators": ["Tool: syft-v1.0.0"]
  },
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-lodash",
      "name": "lodash",
      "versionInfo": "4.17.21",
      "downloadLocation": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "filesAnalyzed": false,
      "checksums": [{ "algorithm": "SHA256", "checksumValue": "abc123..." }],
      "licenseConcluded": "MIT",
      "supplier": "Organization: lodash maintainers"
    }
  ],
  "relationships": [
    {
      "spdxElementId": "SPDXRef-Package-my-app",
      "relationshipType": "DEPENDS_ON",
      "relatedSpdxElement": "SPDXRef-Package-lodash"
    }
  ]
}

CycloneDX

  • Bắt đầu từ OWASP, light-weight design for security use cases
  • Định dạng: JSON, XML, Protocol Buffers
  • Tích hợp tốt VEX (Vulnerability Exploitability eXchange)
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "serialNumber": "urn:uuid:3e671687-...",
  "version": 1,
  "components": [
    {
      "type": "library",
      "bom-ref": "pkg:npm/lodash@4.17.21",
      "name": "lodash",
      "version": "4.17.21",
      "purl": "pkg:npm/lodash@4.17.21",
      "licenses": [{ "license": { "id": "MIT" } }],
      "hashes": [{ "alg": "SHA-256", "content": "abc123..." }]
    }
  ],
  "dependencies": [
    {
      "ref": "pkg:npm/my-app@1.0.0",
      "dependsOn": ["pkg:npm/lodash@4.17.21"]
    }
  ]
}

So sánh

Tiêu chíSPDXCycloneDX
Chuẩn hoáISO/IEC 5962:2021ECMA-424 (đang process)
MaintainerLinux FoundationOWASP
Focus chínhLicense complianceSecurity / vulnerability
FormatJSON, YAML, tag-value, RDFJSON, XML
PURL supportCó (từ 2.3)Có (native)
VEX integrationNative
Best forLegal/compliance teamDevSecOps pipeline

Trong thực tế: dùng cả hai khi cần. Hầu hết tool (syft, Trivy, cdxgen) có thể export cả hai format.

Package URL (PURL)

PURL chuẩn hoá cách định danh package giữa các ecosystem:

pkg:npm/lodash@4.17.21
pkg:pypi/django@4.2.7
pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0
pkg:golang/github.com/gin-gonic/gin@v1.9.1
pkg:docker/library/postgres@15.4
pkg:cargo/serde@1.0.193

Format chung: pkg:<type>/<namespace>/<name>@<version>?<qualifiers>#<subpath>


3. Tạo SBOM với syft

syft là CLI phổ biến nhất, support ~30 ecosystem.

# Cài
brew install syft

# Scan directory (source code)
syft .

# Scan container image
syft alpine:3.19
syft docker.io/myorg/myapp:v1

# Scan và xuất SBOM
syft . -o spdx-json > sbom.spdx.json
syft . -o cyclonedx-json > sbom.cdx.json
syft . -o table          # human-readable

# Scan binary
syft /usr/bin/curl

Output mẫu:

NAME              VERSION       TYPE
async             3.2.4         npm
express           4.18.2        npm
lodash            4.17.21       npm
...

Tạo SBOM với npm built-in (npm 10+)

npm sbom --sbom-format spdx > sbom.spdx.json
npm sbom --sbom-format cyclonedx > sbom.cdx.json

cdxgen — multi-language

npm install -g @cyclonedx/cdxgen
cdxgen -o bom.json -t nodejs .
cdxgen -o bom.json -t python .
cdxgen -o bom.json -t java .

SBOM cho container image

# Build image
docker build -t myapp:v1 .

# Sinh SBOM cho image (gồm cả OS package layer)
syft myapp:v1 -o spdx-json > myapp-sbom.json

# Attach SBOM vào image như attestation (cosign + Sigstore)
cosign attest --predicate myapp-sbom.json \
  --type spdxjson \
  myapp:v1

4. Dependency scanning — phát hiện CVE

CVE và CVSS basics

CVE-2021-44228(Log4Shell)CVEID:CVE-<year>-<sequence>Môt:ApacheLog4j2<2.15.0JNDIlookupRCECVSSv3.1score:10.0(CRITICAL)AttackVector(AV):NetworkAttackComplexity(AC):LowPrivilegesRequired(PR):NoneUserInteraction(UI):NoneScope:ChangedConfidentiality/Integrity/Availabilityimpact:High/High/HighReferences:CISA,Apacheadvisory,exploitDB

Severity scale (CVSS v3.1)

ScoreSeverity
0.1 - 3.9Low
4.0 - 6.9Medium
7.0 - 8.9High
9.0 - 10.0Critical

Severity vs Exploitability — khác biệt quan trọng

CVE severity = "Worst case impact NẾU bị exploit"
Exploitability = "Khả năng thực sự exploit trong app của bạn"

Ví dụ: CVE critical trong dep, nhưng:
- App bạn không call function bị vulnerable → exploit không reach được
- App bạn không expose tới untrusted input → attacker không trigger được
- Dep chỉ chạy ở dev env (devDependency) → production không ảnh hưởng

→ Severity 10.0 không tự động = "patch ngay"
   Cần đánh giá exploitability cụ thể trong context

Tool theo ecosystem

Node:    npm audit, pnpm audit, yarn audit
Python:  pip-audit, safety
Ruby:    bundle audit
Go:      govulncheck (Google official)
Java:    OWASP Dependency-Check, Snyk
Rust:    cargo audit
Multi:   Snyk, GitHub Dependabot, Renovate, Mend (WhiteSource)

npm audit

npm audit

# Output:
# found 5 vulnerabilities (2 moderate, 2 high, 1 critical)
# To address all issues, run:
#   npm audit fix
# To address all issues (including breaking changes), run:
#   npm audit fix --force

# Chi tiết
npm audit --json

# Chỉ fail trên high+
npm audit --audit-level=high

pip-audit

pip install pip-audit
pip-audit
pip-audit -r requirements.txt

# Output:
# Found 2 known vulnerabilities in 1 package
# Name      Version  ID                  Fix Versions
# urllib3   1.26.5   GHSA-mh33-7rrq-662w 1.26.18

govulncheck — chỉ flag vuln EXPLOITABLE

go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

# Khác biệt: govulncheck phân tích call graph
# Chỉ báo CVE nếu code thực sự GỌI function bị vulnerable
# → giảm false positive đáng kể

Snyk (commercial)

npm install -g snyk
snyk auth
snyk test                    # scan deps
snyk container test myapp:v1 # scan container
snyk iac test                # scan Terraform/K8s
snyk code test               # SAST

5. GitHub Dependabot và Renovate

Dependabot

GitHub native, miễn phí cho mọi repo:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    open-pull-requests-limit: 10
    groups:
      minor-and-patch:
        update-types: ["minor", "patch"]
    ignore:
      - dependency-name: "react"
        versions: [">=19.0.0"]   # tạm hold major

  - package-ecosystem: docker
    directory: /
    schedule:
      interval: weekly

  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: monthly

Dependabot auto tạo PR khi:

  • Có security advisory (urgent, dùng dependabot/security-updates)
  • Có version mới (scheduled)

Renovate (Mend) — mạnh hơn

// renovate.json
{
  "extends": ["config:base"],
  "schedule": ["after 9pm every weekday", "before 5am every weekday"],
  "packageRules": [
    {
      "matchUpdateTypes": ["minor", "patch"],
      "automerge": true,
      "automergeType": "branch"
    },
    {
      "matchDepTypes": ["devDependencies"],
      "automerge": true
    },
    {
      "matchPackagePatterns": ["^@types/"],
      "automerge": true,
      "groupName": "@types packages"
    }
  ],
  "vulnerabilityAlerts": {
    "labels": ["security"],
    "schedule": ["at any time"]
  }
}

Renovate ưu việt hơn Dependabot:

  • Group nhiều update vào 1 PR (giảm noise)
  • Auto-merge thông minh hơn
  • Schedule chi tiết hơn
  • Support nhiều ecosystem hơn (Terraform, Helm, Kustomize)

6. Container image scanning

Trivy — Aqua Security (open source, miễn phí)

brewinstalltrivy#Scanimagetrivyimagealpine:3.19trivyimagemyorg/myapp:v1#Output:#alpine:3.19(alpine3.19.0)#Total:3(UNKNOWN:0,LOW:0,MEDIUM:1,HIGH:2,CRITICAL:0)###LibraryVulnIDSeverityVersionFixed##libcrypto3CVE-2024..HIGH3.1.43.1.5##Scanfilesystemtrivyfs.#ScanIaC(Terraform,K8s,Dockerfile)trivyconfig.#Scansecretstrivyfs--scannerssecret.#GenerateSBOMtrivyimage--formatspdx-jsonmyapp:v1>sbom.jsontrivyimage--formatcyclonedxmyapp:v1>sbom.cdx.json#ScanSBOMfile(offline)trivysbomsbom.cdx.json

Grype + syft (Anchore stack)

brew install grype

# Scan image
grype alpine:3.19
grype myapp:v1

# Scan từ SBOM (offline, fast)
syft myapp:v1 -o json > sbom.json
grype sbom:sbom.json

So sánh Trivy vs Grype

TrivyGrype
VendorAqua SecurityAnchore
LicenseApache 2.0Apache 2.0
Scan typesImage, filesystem, IaC, secret, SBOMImage, filesystem, SBOM
DB sourcesNVD, GHSA, OSV, distro advisoriesNVD, GHSA, distro advisories
SpeedNhanh, single binaryNhanh khi dùng với pre-built SBOM
IaC scanCó (Terraform, K8s, Dockerfile)Không (dùng anchore-engine riêng)

Khuyến nghị thực tế: Trivy cho one-stop scanning. syft + grype khi cần separation between SBOM generation và scanning (ví dụ: scan offline trong air-gapped env).


7. Tích hợp vào CI

GitHub Actions — fail build trên HIGH+

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: "0 6 * * *"   # daily 6am UTC

jobs:
  deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: npm audit
        run: npm audit --audit-level=high
        continue-on-error: false

      - name: pip-audit
        if: hashFiles('requirements.txt') != ''
        run: |
          pip install pip-audit
          pip-audit -r requirements.txt

  container:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: HIGH,CRITICAL
          exit-code: "1"           # fail build nếu có HIGH/CRITICAL
          ignore-unfixed: true     # skip CVE chưa có fix

      - name: Upload SARIF to GitHub Security
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          format: spdx-json
          output-file: sbom.spdx.json

      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json

Allowlist khi cần (.trivyignore)

# .trivyignore — ignore CVE chưa có fix hoặc không exploitable
# Mỗi CVE phải có comment lý do và ngày review lại

# CVE-2024-1234: chỉ ảnh hưởng function chúng ta không gọi (path X.Y.Z)
# Review lại: 2026-12-31
CVE-2024-1234

# CVE-2024-5678: dep chỉ dùng trong dev/test
CVE-2024-5678

Quy tắc allowlist:

  • Mọi entry phải có expiry date — không allowlist vĩnh viễn
  • Comment phải explain vì sao không fix, không chỉ "ignore"
  • Review allowlist hàng quý

Branch protection — block merge khi có critical CVE

GitHub repo Settings → Branches → Branch protection rules:
- Require status checks before merge:
  ✓ Security Scan / deps
  ✓ Security Scan / container

8. VEX — giảm noise

Vấn đề: scan tool báo 50 CVE — bao nhiêu thực sự ảnh hưởng?
        Triage thủ công tốn rất nhiều giờ engineer.

VEX (Vulnerability Exploitability eXchange): document machine-readable
nói "CVE X có trong dep Y nhưng KHÔNG affected vì lý do Z"

CycloneDX VEX example

{
  "vulnerabilities": [
    {
      "id": "CVE-2024-1234",
      "source": { "name": "NVD" },
      "affects": [{ "ref": "pkg:npm/express@4.18.2" }],
      "analysis": {
        "state": "not_affected",
        "justification": "vulnerable_code_not_in_execute_path",
        "detail": "Vulnerable function `express.unsafeRender` chưa được dùng trong app. Verified bởi static analysis tool X, last reviewed 2025-06-15."
      }
    }
  ]
}

state có thể là:

  • resolved — đã patch
  • exploitable — confirm ảnh hưởng
  • not_affected — không ảnh hưởng (kèm justification)
  • false_positive — sai từ scan tool
  • in_triage — đang review

VEX cho phép automation: CI thấy CVE → check VEX → nếu có entry not_affected thì skip alert. Giảm false-positive fatigue.


9. Câu hỏi ôn tập

  1. Vì sao SBOM trở thành yêu cầu compliance sau sự cố Log4Shell?

    Xem đáp án

    Khi Log4Shell (CVE-2021-44228) công bố tháng 12/2021, mọi tổ chức phải trả lời câu hỏi đơn giản: "Chúng ta có dùng log4j không? Phiên bản nào? Trong những app nào?" Không có SBOM, đội bảo mật phải scan thủ công hàng trăm app, mất nhiều ngày — trong khi attacker đã exploit hàng giờ sau khi PoC public.

    Hệ quả: US Executive Order 14028 (5/2021) yêu cầu SBOM cho mọi software bán cho US gov. EU Cyber Resilience Act (2024) tương tự. SBOM trở thành công cụ chuẩn để incident response, vulnerability management, license compliance, và supply chain audit. Với SBOM, query "ai dùng log4j 2.x" trở thành SQL one-liner thay vì hàng nghìn giờ effort.

  2. SPDX và CycloneDX khác nhau như thế nào, và khi nào nên dùng cái nào?

    Xem đáp án

    SPDX: chuẩn ISO/IEC 5962:2021, maintained bởi Linux Foundation từ 2010. Focus mạnh vào license compliance — SPDX License List là chuẩn license identifier toàn ngành. Định dạng đa dạng (JSON, YAML, RDF, tag-value).

    CycloneDX: bắt đầu từ OWASP, light-weight, focus mạnh vào security use case. Native support cho VEX (Vulnerability Exploitability eXchange). Định dạng JSON/XML.

    Khi nào dùng:

    • SPDX cho legal/compliance team (license audit, US gov submissions yêu cầu SPDX cụ thể)
    • CycloneDX cho DevSecOps pipeline (tích hợp scanning, VEX, dễ generate)
    • Thực tế: nhiều tool (syft, Trivy) export cả hai, vendor cả hai khi cần.
  3. Severity của CVE và exploitability trong app cụ thể khác nhau như thế nào? Cho ví dụ.

    Xem đáp án

    Severity (CVSS score): đo "worst case impact NẾU CVE bị exploit thành công". Tính trên kịch bản tổng quát, không biết context cụ thể của app.

    Exploitability trong app: thực tế CVE có reach được trong app của bạn không, dựa vào: (1) Code vulnerable có được CALL không (call graph analysis)? (2) Untrusted input có chạm tới function đó không? (3) Dep dùng ở runtime hay chỉ dev (devDependency)?

    Ví dụ: CVE-2024-XYZ trong thư viện image processing với CVSS 9.8 (Critical) — buffer overflow khi parse exotic image format. Nếu app của bạn chỉ dùng thư viện đó để resize JPG (function khác hoàn toàn) và không bao giờ gọi function vulnerable → exploitability = 0 trong thực tế, dù severity = 9.8.

    Đó là lý do tools như govulncheck (Go) phân tích call graph để chỉ flag CVE actually reachable, giảm false positive. VEX standard giúp document/communicate điều này.

  4. Vì sao Renovate hoặc Dependabot tự động merge minor/patch update vẫn cần caution?

    Xem đáp án

    Auto-merge minor/patch dựa trên giả định SemVer chuẩn: minor/patch chỉ thêm tính năng/sửa lỗi, không breaking. Vấn đề:

    (1) Maintainer không tuân thủ SemVer: nhiều lib break trong patch (đặc biệt npm ecosystem). Auto-merge có thể đẩy bug breaking lên prod.

    (2) Supply chain attack: event-stream và ua-parser-js bị compromise qua patch update. Nếu auto-merge mọi patch, mã độc vào production trong vài giờ.

    (3) Behavioral change: lib có thể đổi log format, metrics, default behavior trong "patch" — không breaking API nhưng break monitoring/operations.

    Best practice: (a) auto-merge chỉ devDependencies + types, (b) require human review cho runtime deps, (c) chạy full test suite + smoke test trước merge, (d) deploy auto-merge xuống staging trước, soak time vài giờ trước khi roll prod, (e) ưu tiên security alerts (faster lane) nhưng vẫn require status check.

  5. Container image scanning với Trivy phát hiện vuln ở những layer nào, và pattern "ignore-unfixed" có ý nghĩa gì?

    Xem đáp án

    Trivy scan nhiều layer trong image:

    • OS package layer: Alpine apk, Debian/Ubuntu dpkg, RHEL rpm — check distro advisories
    • Application dep layer: node_modules, Python packages, Go modules trong image — check NVD/GHSA
    • Secrets: hardcoded API key, private key trong layers
    • Misconfiguration: Dockerfile bad practices (USER root, COPY .env, etc.)

    --ignore-unfixed: skip CVE chưa có version fix nào available. Lý do: nhiều CVE trong distro package (Alpine, Debian) công bố trước khi distro maintainer release patch. Báo những CVE này là "noise actionable" — bạn không thể fix bằng cách update vì update chưa tồn tại. Ignore cho đến khi có fix → giảm alert fatigue và tập trung vào CVE có thể action.

    Cẩn thận: vẫn cần background scan định kỳ để biết khi nào fix có sẵn để upgrade.

Bài tập thực hành

# 1. Tạo SBOM cho project hiện tại
brew install syft trivy grype
cd ~/your-node-project

syft . -o spdx-json > sbom.spdx.json
syft . -o cyclonedx-json > sbom.cdx.json
syft . -o table             # human-readable preview

# 2. Scan với npm audit và pip-audit
npm audit --audit-level=high
# Nếu Python:
pip install pip-audit
pip-audit -r requirements.txt

# 3. Scan SBOM với grype (offline)
grype sbom:sbom.cdx.json

# 4. Build image và scan với Trivy
cat > Dockerfile <<EOF
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "start"]
EOF
docker build -t my-app:test .
trivy image my-app:test
trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:test
trivy image --format spdx-json my-app:test > image-sbom.json

# 5. Scan Dockerfile và IaC
trivy config Dockerfile
trivy config .   # scan toàn bộ Terraform/K8s nếu có

# 6. Setup Dependabot config
mkdir -p .github
cat > .github/dependabot.yml <<EOF
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    open-pull-requests-limit: 10
  - package-ecosystem: docker
    directory: /
    schedule:
      interval: weekly
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: monthly
EOF
git add .github/dependabot.yml
git commit -m "chore: enable Dependabot"

# 7. Trigger Trivy scan trong CI (.github/workflows/security.yml)
# Xem ví dụ trong phần 7 của bài

Tài liệu tham khảo chính thức


Tiếp theo: Ngày 18 — Quiz Tổng Kết Tuần 3