← DevSecOps← DevSecOps
DevSecOpsDevSecOps19 Th7, 2026Jul 19, 202634 phút đọc27 min read

Bảo mật CI/CD & Supply ChainCI/CD & Supply Chain Security

Thuộc bộ kiến thức DevSecOps Roadmap.

Tổng quan

Bảo mật CI/CD và supply chain là phần lõi vận hành của DevSecOps — nơi “shift-left” không còn là khẩu hiệu mà trở thành automation cụ thể chạy trên từng commit. Một build pipeline không chỉ là dây chuyền lắp ráp; đó là một hệ thống có quyền truy cập (privileged) vào source code, secrets, credentials cloud, và có khả năng đẩy artifact thẳng vào production. Nếu attacker chiếm được pipeline, mọi security control ở downstream (code review, testing, monitoring) đều có thể bị bỏ qua hoàn toàn, vì lúc này attacker đang “ship” chính artifact của họ thông qua quy trình mà bạn tin tưởng.

Đây không phải chuyện lý thuyết. Vụ SolarWinds Orion (2020) liên quan đến việc attacker cấy mã độc trực tiếp vào build process, khiến phần mềm được ký hợp lệ, phát hành chính thức lại mang backdoor tới ~18.000 khách hàng. Vụ xâm nhập Codecov bash uploader (2021) sửa đổi một CI script mà hàng nghìn pipeline thực thi, từ đó exfiltrate environment variables (bao gồm cả secrets) từ mọi pipeline chạy script đó. Vụ backdoor xz-utils (2024) cho thấy ngay cả build script của một thư viện nén mã nguồn mở được tin cậy rộng rãi cũng có thể bị phá hoại bởi một kẻ chiếm quyền maintainer kiên nhẫn, có kỹ thuật social engineering. Mỗi vụ này đều là một supply chain attack: mục tiêu không bao giờ là nạn nhân trực tiếp, mà là một thành phần upstream được tin cậy — source repository, build system, dependency, hay kênh phân phối — mà nạn nhân ngầm định tin tưởng.

Bài viết này bao quát hai mối quan tâm gắn chặt với nhau:

  1. Pipeline hardening — bảo mật chính hệ thống CI/CD (runners, secrets, branch protection, tính toàn vẹn của commit) để attacker không thể can thiệp vào build process.
  2. Supply chain security — bảo mật mọi thứ mà pipeline tiêu thụ và tạo ra: nguồn gốc (provenance) của source code, dependency bên thứ ba, build output, SBOM, chữ ký (signature), và attestation, để người tiêu thụ phần mềm của bạn có thể xác minh nó chứa gì và đến từ đâu.

Bộ công cụ scanning bảo mật (SAST, DAST, SCA, IaC scanning, secrets scanning, container scanning) chính là lớp automation biến cả hai điều trên thành hoạt động liên tục thay vì một cuộc audit một lần. Phần còn lại của bài viết sẽ đi qua từng lớp, ghép chúng lại trong một pipeline GitHub Actions thực tế, và trình bày các khái niệm supply chain (SBOM, SLSA, Sigstore) cho phép bạn chứng minh — chứ không chỉ tuyên bố — rằng một artifact đủ an toàn để deploy.

Chủ đề liên quan: Secure Coding and Web Application Security, Container and Kubernetes Security, DevOps — CI/CD, DevOps — Infrastructure as Code.

Kiến thức nền tảng

Pipeline hardening

Pipeline là mục tiêu giá trị cao chính vì nó vốn được thiết kế để có quyền truy cập rộng: nó checkout source code, pull dependencies, thường nắm giữ credentials deploy cloud, và tạo ra artifact sẽ chạy trên production. Hardening pipeline nghĩa là áp dụng cùng kỷ luật access-control và isolation mà bạn áp dụng cho một hệ thống production.

ControlNó làm gìVì sao quan trọng
CI runner theo least-privilegeGiới hạn credentials của mỗi job (IAM role của cloud, registry token, deploy key) chỉ ở mức job đó thực sự cần, dùng token ngắn hạn, có phạm vi hẹp thay vì token tĩnh dài hạnMột job bị xâm phạm (ví dụ qua một dependency độc hại) chỉ có thể gây thiệt hại trong giới hạn quyền hẹp đó
Build environment ephemeral / isolatedDùng container hoặc VM mới, dùng một lần cho mỗi job (GitHub-hosted runners, ephemeral Kubernetes pod runners, EC2 dùng-rồi-bỏ) thay vì build server chia sẻ, tồn tại lâu dàiNgăn trạng thái còn sót lại hoặc việc bị xâm phạm ở một job ảnh hưởng tới build tiếp theo; loại bỏ mục tiêu tĩnh để attacker cấy persistence
Isolation cho self-hosted runnerNếu tự host runner (vì chi phí, network access, hay phần cứng đặc thù), không bao giờ chia sẻ runner giữa PR từ fork công khai và branch tin cậy, và không tái sử dụng runner cho các workload không tin cậyMột pull request độc hại từ fork nếu không sẽ có thể thực thi mã tùy ý với quyền truy cập vào secrets vốn chỉ dành cho branch tin cậy (một lỗi cấu hình GitHub Actions rất phổ biến trong thực tế)
Signed commitsYêu cầu commit (và lý tưởng là cả tag) được ký bằng GPG, SSH, hoặc gitsign của Sigstore, và enforce việc verify ở phía server (ví dụ rule “Require signed commits” của GitHub)Chứng minh commit thực sự đến từ key của tác giả được khai báo, khiến việc âm thầm chèn lịch sử độc hại khó hơn nhiều, và cung cấp non-repudiation cho audit
Protected branch / branch policyYêu cầu pull request review (kèm CODEOWNERS cho các đường dẫn nhạy cảm), status check phải pass, branch phải up-to-date, lịch sử tuyến tính, và tắt force-push/xóa trên branch main/releaseNgăn một tài khoản duy nhất bị xâm phạm hoặc độc hại push thẳng vào branch dẫn tới production, đảm bảo mọi thay đổi đều qua review + gate tự động
Secrets không bao giờ nằm trong code hay logLưu secrets trong secret manager chuyên dụng (GitHub/GitLab CI secrets, HashiCorp Vault, AWS Secrets Manager) — không bao giờ trong source, config file, hay Docker layer; che (mask) giá trị secret trong log output; dọn dẹp debug/verbose logging có thể vô tình in ra environment variablesSource repo và CI logs thường tồn tại lâu dài, nhiều người đọc được, và được backup/cache ở những nơi bạn không kiểm soát — một secret bị lộ ở đó có thể tồn tại lâu hơn cả sự cố gây ra nó, tính bằng năm
Ưu tiên OIDC federation thay vì cloud credentials tĩnhDùng token ngắn hạn, được liên kết workload-identity (GitHub OIDC → AWS IAM role / GCP Workload Identity Federation / Azure federated credentials) thay vì lưu access key cloud dài hạn dưới dạng CI secretLoại bỏ hoàn toàn một loại secret (cloud key tĩnh) có thể bị lộ, exfiltrate, hoặc tái sử dụng ngoài phạm vi pipeline; token có phạm vi hẹp, ngắn hạn, và gắn về mặt cryptographic với một workflow run cụ thể
Pin Actions/plugin bên thứ ba theo commit SHATham chiếu các bước workflow tái sử dụng (ví dụ actions/checkout@<full-sha>) bằng commit hash bất biến, không dùng tag có thể thay đổi như @v4Một tag có thể bị chính maintainer của action (hoặc attacker chiếm được tài khoản của họ) trỏ sang mã độc mà không cần thay đổi gì trong workflow file của bạn
Yêu cầu approval cho workflow từ forkCấu hình “Require approval for first-time contributors” / “Require approval for all outside collaborators” trên repo công khaiNgăn pull request “đánh nhanh rút gọn” thực thi mã CI tùy ý (ví dụ cryptomining, exfiltrate secrets) trước khi maintainer review

Nguyên tắc xuyên suốt tất cả những điều trên là: coi pipeline như một hệ thống production, không phải một tiện ích nội bộ. Bất kỳ ai có thể sửa cấu hình pipeline, thêm dependency, hoặc merge vào branch được bảo vệ đều có, một cách gián tiếp, khả năng ảnh hưởng tới những gì được ship ra production — access control cần phản ánh điều đó.

Bộ công cụ scanning bảo mật

Scanning tự động là thứ biến shift-left thành một quá trình liên tục thay vì một cuộc audit một lần. Mỗi nhóm dưới đây trả lời một câu hỏi khác nhau, chạy ở một điểm khác nhau trong vòng đời, và bắt được một loại vấn đề khác nhau — không loại nào thay thế được loại nào.

SAST — Static Application Security Testing

SAST phân tích source code (hoặc bytecode/binary) mà không thực thi nó, tìm các pattern không an toàn: nối chuỗi SQL có thể dẫn đến injection, dùng thuật toán mã hóa yếu, deserialization không an toàn, credential hardcode, thiếu input validation, và các API được biết là nguy hiểm. SAST chạy nhanh và sớm nhất — thường trên mỗi pull request, thậm chí ngay trong IDE trước khi commit.

Điểm mạnh của SAST: phát hiện vấn đề sớm, trước cả khi code chạy, với khả năng nhìn thấy toàn bộ logic. Điểm yếu của SAST: không có context runtime, nên không thể thấy app thực sự hoạt động ra sao khi deploy (quyền runtime bị cấu hình sai, mức độ expose network thực tế), và có xu hướng tạo false positive trên các pattern thực chất an toàn trong ngữ cảnh cụ thể.

DAST — Dynamic Application Security Testing

DAST kiểm thử một ứng dụng đang chạy từ bên ngoài, theo đúng cách một attacker bên ngoài sẽ làm — gửi input độc hại tới các HTTP endpoint đang sống, fuzz tham số, dò tìm injection, broken authentication, cấu hình sai, và các vấn đề khác thuộc lớp OWASP Top 10, mà không cần biết gì về source code (black-box). DAST bắt buộc chạy muộn hơn trong pipeline, nhắm vào một instance đã được deploy (staging, review app, hoặc môi trường ephemeral dựng riêng cho lần chạy pipeline).

Xem Container and Kubernetes Security để biết cách harden môi trường runtime mà DAST scan vào. Chi tiết cách dùng công cụ: xem bài viết về security testing tools (dự kiến; chưa được xuất bản trong knowledge base này — phần thiết lập công cụ DAST và tuning scan sẽ nằm ở đó).

SCA — Software Composition Analysis

SCA scan dependency khai báo và transitive (npm, pip, Maven, Go modules, v.v.) đối chiếu với các cơ sở dữ liệu lỗ hổng (NVD, GitHub Advisory Database, OSV) để phát hiện CVE đã biết, và thường cũng gắn cờ các license open-source có vấn đề (ví dụ GPL trong codebase độc quyền). Điều này rất quan trọng vì phần lớn code trong một ứng dụng hiện đại không phải do bạn viết — mà là dependency, và dependency-của-dependency.

SCA là nơi phần lớn các vụ breach thực tế bắt nguồn — một dependency có lỗ hổng (ví dụ Log4Shell trong log4j, 2021) có thể gây ảnh hưởng lớn hơn nhiều so với một bug trong chính code của bạn, vì một thư viện có lỗ hổng thường được hàng nghìn dự án downstream dùng cùng lúc. Xem thêm hướng dẫn về secure coding và dependency risk management trong Secure Coding and Web Application Security.

IaC scanning

IaC scanning (Infrastructure as Code scanning) phân tích tĩnh Terraform, CloudFormation, Kubernetes manifest, và các định nghĩa hạ tầng khai báo tương tự để tìm cấu hình không an toàn trước khi bất cứ thứ gì được provision — một security group mở, một S3 bucket không mã hóa, một IAM policy quá rộng quyền, một container chạy với quyền root. Về bản chất đây là “SAST cho hạ tầng”, và nên nằm cùng giai đoạn pipeline với SAST vì nó cũng static và nhanh.

Nội dung đầy đủ về thiết kế IaC, quản lý state, và module nằm trong DevOps — Infrastructure as Code; bài viết này chỉ tập trung vào khía cạnh scanning/gating khi nó gắn vào pipeline.

Secrets scanning

Secrets scanning phát hiện credential hardcode, API key, private key, và token được commit vào source — cả trong working tree hiện tại lẫn, quan trọng không kém, trong lịch sử commit (một secret đã bị xóa ở commit sau vẫn bị lộ với bất kỳ ai có thể git log/git clone toàn bộ lịch sử). Đây là một trong những loại scan có ROI cao nhất, ít nhiễu nhất, vì một kết quả khớp gần như không bao giờ là false positive.

Best practice là chạy secrets scanning trước cả khi commit rời khỏi máy developer (pre-commit hook), lại chạy trên mỗi pull request, và định kỳ chạy full-history scan, vì các pattern phát hiện mới được bổ sung theo thời gian và có thể bắt được secret đã tồn tại từ trước khi rule đó ra đời.

Container image scanning

Container image scanning kiểm tra các OS package, dependency ở tầng ngôn ngữ, và cấu hình của một image đã build để tìm lỗ hổng đã biết và cấu hình sai (chạy với quyền root, thiếu HEALTHCHECK, capability không cần thiết, secrets bị nướng vào layer). Nó chạy sau khi image được build, trước khi push lên registry hoặc deploy.

Hướng dẫn đầy đủ về hardening container và Kubernetes (chọn base image, non-root user, read-only filesystem, admission control, runtime security) nằm trong Container and Kubernetes Security; bài viết này chỉ tập trung vào vị trí image scanning trong pipeline và cách nó gate việc deploy.

So sánh các nhóm công cụ

NhómCâu hỏi nó trả lờiBắt được gìChạy khi nàoĐiểm mù
SAST”Code của tôi có được viết an toàn không?”Pattern injection, mã hóa yếu, deserialization không an toàn, secret hardcode trong logicTrên mỗi commit/PR, feedback nhanh nhấtKhông có context runtime; false positive với code an toàn trong ngữ cảnh
DAST”Ứng dụng đang chạy của tôi có hành xử an toàn khi bị tấn công không?”Broken auth, injection có thể tiếp cận qua HTTP, cấu hình sai, OWASP Top 10Nhắm vào instance đã deploy (staging/review app), chậm hơnCần target đang chạy; độ phủ code giới hạn ở các đường được thực thi
SCA”Dependency của tôi có an toàn không?”CVE đã biết trong dependency trực tiếp/transitive, vi phạm licenseTrên mỗi build, và liên tục khi CVE mới được công bốKhông bắt được lỗ hổng chưa có trong CVE database (0-day), hay việc lạm dụng một thư viện an toàn
IaC scanning”Hạ tầng của tôi có được định nghĩa an toàn không?”Security group mở, bucket công khai, IAM quá rộng quyền, thiếu mã hóaTrên mỗi commit/PR chạm vào IaC, nhanhKhông kiểm tra được sự lệch (drift) thực tế giữa hạ tầng đã deploy và định nghĩa IaC
Secrets scanning”Tôi có commit nhầm credential không?”API key, token, private key, password trong code hoặc lịch sửPre-commit và trên mỗi push, rất nhanhCó thể bỏ sót secret có định dạng/entropy khác thường; không bắt được secret chỉ được inject lúc runtime
Container image scanning”Image đã build của tôi có an toàn để chạy không?”Package OS/app có lỗ hổng trong image, chạy quyền root, Dockerfile viết không tốtSau khi build, trước khi push/deployKhông bắt được lỗ hổng ở logic ứng dụng (đó là việc của SAST/DAST)

Không nhóm nào thay thế được nhóm khác — một pipeline trưởng thành chạy tất cả, mỗi nhóm đóng vai trò một lớp defense-in-depth khác nhau áp dụng riêng cho quá trình build/release.

Khái niệm chính

Một pipeline GitHub Actions thực tế: build → scan → gate → deploy

Workflow dưới đây ghép SAST, SCA, secrets scanning, container scanning, sinh SBOM, và ký artifact vào một pipeline duy nhất, gate job deploy dựa trên việc mọi scan đều pass. Pipeline dùng OIDC để assume một cloud role thay vì credentials tĩnh, và pin action để ví dụ sát thực tế (SHA rút gọn được dùng ở đây cho dễ đọc — trong thực tế nên pin theo SHA đầy đủ 40 ký tự).

name: build-scan-deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read        # least privilege by default; elevate per-job as needed
  id-token: write        # required for OIDC federation to cloud provider
  security-events: write  # required to upload SARIF to the Security tab

env:
  IMAGE_NAME: ghcr.io/${{ github.repository }}/app

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4  # pin to full commit SHA in production

      - name: Set up build tooling
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies (locked, no scripts)
        run: npm ci --ignore-scripts

      - name: Build image
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          outputs: type=docker,dest=/tmp/app-image.tar

      - name: Upload image artifact for downstream jobs
        uses: actions/upload-artifact@v4
        with:
          name: app-image
          path: /tmp/app-image.tar
          retention-days: 1

  sast:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript

      - name: Run CodeQL analysis
        uses: github/codeql-action/analyze@v3   # uploads SARIF automatically

      - name: Semgrep scan
        uses: semgrep/semgrep-action@v1
        with:
          config: p/ci  # curated default ruleset

  secrets-scan:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # full history so gitleaks can scan past commits too

      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITLEAKS_ENABLE_UPLOAD_ARTIFACT: true
        # fails the job (non-zero exit) if any secret is detected

  sca:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4

      - name: Dependency vulnerability scan (Trivy fs mode)
        uses: aquasecurity/trivy-action@0.24.0
        with:
          scan-type: fs
          scan-ref: .
          severity: CRITICAL,HIGH
          exit-code: "1"          # fail the build on critical/high findings
          format: sarif
          output: trivy-fs.sarif

      - name: Upload SCA results to Security tab
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-fs.sarif

  container-scan:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-image
          path: /tmp

      - name: Load image for scanning
        run: docker load --input /tmp/app-image.tar

      - name: Scan built image (Trivy)
        uses: aquasecurity/trivy-action@0.24.0
        with:
          image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: "1"
          format: sarif
          output: trivy-image.sarif
          input: /tmp/app-image.tar

      - name: Upload image scan results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-image.sarif

      - name: Generate SBOM (SPDX, via Syft)
        uses: anchore/sbom-action@v0
        with:
          image: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json

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

  # deploy only runs once every scanning gate above has passed
  deploy:
    needs: [sast, secrets-scan, sca, container-scan]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production   # environment protection rule = manual approval gate
    permissions:
      contents: read
      id-token: write          # OIDC token for cloud auth, no static keys
      packages: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-image
          path: /tmp

      - name: Load and push image
        run: |
          docker load --input /tmp/app-image.tar
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Install cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign image (keyless, via Sigstore/Fulcio + Rekor)
        run: cosign sign --yes ${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Attach SBOM attestation to image
        uses: actions/download-artifact@v4
        with:
          name: sbom
          path: .
      - run: cosign attest --yes --predicate sbom.spdx.json --type spdxjson ${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Assume cloud deploy role via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gh-actions-deploy
          aws-region: us-east-1

      - name: Deploy
        run: ./scripts/deploy.sh ${{ env.IMAGE_NAME }}:${{ github.sha }}

Vài quyết định thiết kế đáng chú ý:

Supply chain security: SBOM

SBOM (Software Bill of Materials) là một bản kiểm kê có cấu trúc, máy đọc được, liệt kê mọi thứ cấu thành nên một phần mềm — dependency trực tiếp và transitive, phiên bản, license, và nguồn gốc của chúng. SBOM trả lời câu hỏi “thực sự bên trong artifact này có gì?”, điều rất quan trọng khi một CVE mới được công bố (ví dụ Log4Shell): có SBOM, bạn có thể grep/query “ai đang dùng log4j-core phiên bản X” trên toàn bộ fleet trong vài phút thay vì phải audit thủ công từng service.

Định dạngĐược duy trì bởiĐiểm mạnh nổi bật
SPDX (Software Package Data Exchange)Linux Foundation, chuẩn ISO/IEC 5962:2021Metadata license-compliance mạnh, tooling trưởng thành, được chuẩn hóa ISO
CycloneDXOWASPThiết kế tập trung vào bảo mật, hỗ trợ native cho dữ liệu vulnerability (VEX) và provenance, nhẹ hơn

Cả hai đều được hỗ trợ rộng rãi bởi các công cụ như Syft, Trivy, và các trình sinh SBOM riêng theo ngôn ngữ (ví dụ cyclonedx-npm, cyclonedx-gomod). Sinh SBOM ngay tại thời điểm build và đính kèm vào release (như một build artifact và/hoặc một attestation đã ký, giống trong pipeline ở trên) cho phép người tiêu thụ downstream — kể cả chính đội incident response tương lai của bạn — trả lời “chúng ta có bị ảnh hưởng bởi CVE-X không” mà không cần scan lại từ đầu.

Supply chain security: provenance, attestation, và SLSA

Provenance là metadata có thể xác minh về cách một artifact được build: từ commit source nào, bởi build system nào, dùng bước build nào, trên platform nào — thay vì tin một artifact chỉ vì nó xuất hiện đúng chỗ trong registry với đúng tên. Attestation là một tuyên bố đã được ký khẳng định một sự thật về artifact (ví dụ “SBOM này thuộc về digest image này”, hoặc “image này được build bởi đúng workflow run CI cụ thể này từ đúng commit cụ thể này”), thường được mã hóa bằng định dạng attestation in-toto.

SLSA (Supply-chain Levels for Software Artifacts), đọc là “salsa”, là một framework (ban đầu từ Google, nay thuộc OpenSSF) định nghĩa các mức độ toàn vẹn supply chain tăng dần:

Mức SLSAYêu cầuChống lại điều gì
Build L0Không có đảm bảo nào
Build L1Build process được script hóa/tự động và sinh ra provenanceCác bước build thủ công, không tài liệu hóa, mang tính ngẫu nhiên
Build L2Provenance được sinh bởi một hosted build service và được kýViệc giả mạo provenance sau khi build xong
Build L3Build chạy trong môi trường isolated, hardened; provenance không thể giả mạo được kể cả bởi chính người dùng của build serviceMột attacker có quyền truy cập vào định nghĩa build can thiệp vào chính build process

GitHub Actions có thể sinh provenance tuân thủ SLSA bằng reusable workflow slsa-framework/slsa-github-generator, workflow này chạy build thực tế trong một generator workflow isolated, do GitHub kiểm soát mà repository gọi nó không thể can thiệp trực tiếp — thỏa mãn yêu cầu “không thể giả mạo” ở các mức SLSA cao hơn. Người tiêu thụ sau đó có thể verify provenance bằng slsa-verifier trước khi tin một artifact tải về, kiểm tra rằng nó thực sự được build từ đúng source repository và workflow như mong đợi, chứ không chỉ là một artifact có đúng tên file tồn tại ở đâu đó.

Supply chain security: ký artifact với Sigstore

Sigstore là một dự án mã nguồn mở (nay thuộc OpenSSF) giúp việc ký artifact trở nên dễ tiếp cận mà không gặp phải nỗi đau truyền thống của việc quản lý private key dài hạn, thông qua ba thành phần:

Tại thời điểm deploy, một admission controller (ví dụ Kyverno hoặc sigstore/policy-controller trong Kubernetes) có thể enforce chính sách “chỉ chạy image được ký bởi đúng danh tính OIDC của CI pipeline của chúng ta”, từ chối bất kỳ image nào — kể cả một image có đúng tên và tag — nếu thiếu chữ ký hợp lệ, có thể verify được từ đúng nguồn mong đợi. Điều này lấp khoảng trống giữa “chúng ta đã scan nó trong CI” và “chúng ta có thể chứng minh tại thời điểm deploy rằng cái đang chạy chính là cái đã được scan”, đó mới là thuộc tính bảo mật thực sự mà tooling supply chain hướng tới — kết quả scan sẽ vô nghĩa nếu không có gì ngăn việc deploy một image khác, chưa được scan, với cùng tag.

Dependency pinning

Pinning nghĩa là khóa một dependency (thư viện, base image, hay CI action) vào đúng một phiên bản bất biến, thay vì một tham chiếu có thể thay đổi và trỏ tới thứ khác vào ngày mai.

Cần pin cái gìMutable (nên tránh)Immutable (nên dùng)
Application dependencyVersion range lỏng, không có lockfile (^1.2.0 không có package-lock.json)Lockfile được commit (package-lock.json, poetry.lock, go.sum) resolve về đúng phiên bản/hash
Container base imageFROM node:20 (tag có thể bị trỏ lại)FROM node:20@sha256:<digest> (content-addressed, bất biến)
CI/CD reusable actionuses: actions/checkout@v4uses: actions/checkout@<full-commit-sha>

Pinning đánh đổi một chút tiện lợi (bạn phải chủ động bump pin) để lấy về khả năng dự đoán và audit lớn hơn nhiều: một build hôm nay và build “giống hệt” một tháng sau dùng đúng input byte-for-byte, và một tag/tài khoản maintainer upstream bị xâm phạm không thể âm thầm thay đổi thứ bạn đang dùng. Điều này cố tình tạo ra căng thẳng với automated patching (bên dưới) — cách giải quyết là vẫn pin, nhưng cập nhật pin thường xuyên và tự động qua PR do bot mở, thay vì để range không được pin.

Automated patching

Nếu pin mọi thứ mà không có gì giữ cho các pin đó được cập nhật, codebase sẽ bị đóng băng theo thời gian — vì vậy pinning và nhịp độ patch tự động là hai nửa của cùng một thực hành, không phải hai thứ đối lập nhau.

Chiến lược gating: fail vs. warn, và tránh scanner fatigue

Không phải finding nào cũng nên chặn build — một pipeline fail trên mọi finding mức low kiểu linting sẽ huấn luyện developer bỏ qua hoặc lách qua nó hoàn toàn (“scanner fatigue”), điều này phá hỏng mục đích ban đầu còn tệ hơn cả việc không scan gì cả. Một chiến lược gating khả thi cần dựa trên rủi ro và nhận biết môi trường:

Mức độ nghiêm trọngTrên feature branch/PRKhi merge vào mainTrước khi deploy production
Critical / đang bị khai thác tích cựcFail buildFail buildFail deploy, không có ngoại lệ
HighFail buildFail buildFail deploy; muốn override phải có exception được ghi lại rõ ràng, có thời hạn
MediumCảnh báo / annotate PR, không chặnCảnh báo, đưa vào backlog kèm SLACảnh báo, nhưng vẫn cho phép deploy
Low / thông tinChỉ hiển thị trong tab SecurityChỉ hiển thịKhông phải là gate

Các biện pháp thực tế để giữ điều này bền vững:

Best Practices

Tài liệu tham khảo

Part of the DevSecOps Roadmap knowledge base.

Overview

CI/CD and supply chain security is the operational core of DevSecOps — it is where “shift-left” stops being a slogan and becomes concrete automation running on every commit. A build pipeline is not just an assembly line; it is a privileged system with access to source code, secrets, cloud credentials, and the ability to push artifacts straight into production. If an attacker compromises the pipeline itself, every security control downstream (code review, testing, monitoring) can be bypassed entirely, because the attacker is now shipping their own artifact through your trusted process.

This is not theoretical. The SolarWinds Orion breach (2020) involved attackers implanting malicious code directly into the build process, so that legitimately signed, officially released software carried a backdoor to ~18,000 customers. The Codecov bash uploader compromise (2021) modified a CI script that thousands of pipelines executed, exfiltrating environment variables (including secrets) from every pipeline that ran it. The xz-utils backdoor (2024) showed that even a widely trusted open-source compression library’s build scripts could be subverted by a patient, socially-engineered maintainer takeover. Each of these was a supply chain attack: the target was never the victim directly, but a trusted upstream component — source repository, build system, dependency, or distribution channel — that the victim implicitly trusted.

This note covers two tightly linked concerns:

  1. Pipeline hardening — securing the CI/CD system itself (runners, secrets, branch protection, commit integrity) so attackers can’t tamper with the build process.
  2. Supply chain security — securing everything the pipeline consumes and produces: source code provenance, third-party dependencies, build outputs, SBOMs, signatures, and attestations, so that consumers of your software can verify what it contains and where it came from.

The scanning toolchain (SAST, DAST, SCA, IaC scanning, secrets scanning, container scanning) is the automation layer that makes both of these continuous rather than a one-time audit. The rest of this note works through each layer, ties them together in a real GitHub Actions pipeline, and covers the supply chain concepts (SBOM, SLSA, Sigstore) that let you prove — not just claim — that an artifact is safe to deploy.

Related topics: Secure Coding and Web Application Security, Container and Kubernetes Security, DevOps — CI/CD, DevOps — Infrastructure as Code.

Fundamentals

Build pipeline hardening

The pipeline is a high-value target precisely because it has broad access by design: it checks out source code, pulls dependencies, often holds cloud deployment credentials, and produces the artifact that ends up running in production. Hardening it means applying the same access-control and isolation discipline you’d apply to a production system.

ControlWhat it doesWhy it matters
Least-privilege CI runnersScope each job’s credentials (cloud IAM role, registry token, deploy key) to only what that specific job needs, using short-lived, scoped tokens instead of long-lived static onesA compromised job (e.g., via a malicious dependency) can only do as much damage as its narrow permission set allows
Ephemeral / isolated build environmentsUse fresh, single-use containers or VMs per job (GitHub-hosted runners, ephemeral Kubernetes pod runners, disposable EC2) instead of long-lived, shared build serversPrevents one job’s compromise or leftover state from persisting and affecting the next build; removes a static target for attackers to implant persistence on
Self-hosted runner isolationIf self-hosting runners (for cost, network access, or specialized hardware), never share runners between public/fork PRs and trusted branches, and never reuse a runner across untrusted workloadsA malicious pull request from a fork can otherwise execute arbitrary code with access to secrets meant for trusted branches (a very common real-world GitHub Actions misconfiguration)
Signed commitsRequire commits (and ideally tags) to be signed with GPG, SSH, or Sigstore’s gitsign, and enforce verification on the server (e.g., GitHub “Require signed commits” branch rule)Proves a commit really came from the claimed author’s key, making it much harder to silently inject malicious history, and provides non-repudiation for audits
Protected/branch policiesRequire pull request review (with CODEOWNERS for sensitive paths), passing status checks, up-to-date branches, linear history, and disable force-push/deletion on main/release branchesPrevents a single compromised or malicious account from pushing directly to production-bound branches, and ensures every change went through review + automated gates
Secrets never in code or logsStore secrets in a dedicated secret manager (GitHub/GitLab CI secrets, HashiCorp Vault, AWS Secrets Manager) — never in source, config files, or Docker layers; mask secret values in log output; scrub debug/verbose logging that might echo environment variablesSource repos and CI logs are frequently long-lived, widely readable, and backed up/cached in places you don’t control — a leaked secret there can outlive the incident that caused it by years
Prefer OIDC federation over static cloud credentialsUse short-lived, workload-identity-federated tokens (GitHub OIDC → AWS IAM role / GCP Workload Identity Federation / Azure federated credentials) instead of storing long-lived cloud access keys as CI secretsRemoves an entire class of secret (the static cloud key) that can be leaked, exfiltrated, or reused outside the pipeline; tokens are scoped, short-lived, and tied cryptographically to a specific workflow run
Pin third-party Actions/plugins to a commit SHAReference reusable workflow steps (e.g., actions/checkout@<full-sha>) by immutable commit hash, not a mutable tag like @v4A tag can be moved by the action’s maintainer (or an attacker who compromises their account) to point at malicious code without changing your workflow file at all
Require approval for workflows from forksConfigure “Require approval for first-time contributors” / “Require approval for all outside collaborators” on public reposPrevents drive-by pull requests from executing arbitrary CI code (e.g., cryptomining, secret exfiltration) before a maintainer reviews them

The unifying principle across all of these is: treat the pipeline as a production system, not an internal convenience. Anyone who can modify pipeline configuration, add a dependency, or merge to a protected branch has, transitively, some ability to influence what ships to production — access controls should reflect that.

The security scanning toolchain

Automated scanning is what makes shift-left continuous instead of a one-off audit. Each category below answers a different question, runs at a different point in the lifecycle, and catches a different class of issue — none of them is a substitute for the others.

SAST — Static Application Security Testing

SAST analyzes source code (or bytecode/binaries) without executing it, looking for insecure patterns: SQL string concatenation that could allow injection, use of weak cryptographic primitives, unsafe deserialization, hardcoded credentials, missing input validation, and known-dangerous API usage. It runs fastest and earliest — typically on every pull request, sometimes even in the IDE before commit.

SAST strength: finds issues early, before code even runs, with full visibility into logic. SAST weakness: no runtime context, so it can’t see how the app actually behaves once deployed (misconfigured runtime permissions, real network exposure), and tends to produce false positives on patterns that are actually safe in context.

DAST — Dynamic Application Security Testing

DAST tests a running application from the outside, the same way an external attacker would — sending malicious inputs to live HTTP endpoints, fuzzing parameters, probing for injection, broken authentication, misconfigurations, and other OWASP Top 10-class issues, without any knowledge of the source code (black-box). It necessarily runs later in the pipeline, against a deployed instance (staging, a review app, or an ephemeral environment spun up for the pipeline run).

See Container and Kubernetes Security for hardening the runtime environment DAST scans against. Detailed tool usage: see the security testing tools note (planned; not yet published in this knowledge base — DAST tool setup and scan tuning will live there).

SCA — Software Composition Analysis

SCA scans your declared and transitive dependencies (npm, pip, Maven, Go modules, etc.) against vulnerability databases (NVD, GitHub Advisory Database, OSV) to flag known CVEs, and often also flags problematic open-source licenses (e.g., GPL in a proprietary codebase). This matters enormously because the vast majority of a modern application’s code is not code you wrote — it’s dependencies, and dependencies-of-dependencies.

SCA is where most real-world breaches originate in practice — a vulnerable dependency (e.g., Log4Shell in log4j, 2021) can be far more impactful than a bug in your own code, because a single vulnerable library is often used by thousands of downstream projects simultaneously. See secure coding and dependency risk management guidance in Secure Coding and Web Application Security.

IaC scanning

Infrastructure as Code scanning statically analyzes Terraform, CloudFormation, Kubernetes manifests, and similar declarative infra definitions for insecure configuration before anything is provisioned — an open security group, an unencrypted S3 bucket, an overly permissive IAM policy, a container running as root. This is essentially “SAST for infrastructure,” and it belongs in the same pipeline stage as SAST since it’s static and fast.

Full coverage of IaC design, state management, and modules lives in DevOps — Infrastructure as Code; this note focuses only on the scanning/gating aspect as it plugs into the pipeline.

Secrets scanning

Secrets scanning detects hardcoded credentials, API keys, private keys, and tokens committed to source — both in the current working tree and, critically, in commit history (a secret removed in a later commit is still exposed to anyone who can git log/git clone the full history). This is one of the highest ROI, lowest-noise scans, because a match is very rarely a false positive.

Best practice is to run secrets scanning before the commit even leaves the developer’s machine (pre-commit hook), again on every pull request, and periodically as a full-history scan, since new detection patterns are added over time and may catch secrets that predate the rule.

Container image scanning

Container image scanning inspects a built image’s OS packages, language-level dependencies, and configuration for known vulnerabilities and misconfiguration (running as root, missing HEALTHCHECK, unnecessary capabilities, exposed secrets baked into layers). It runs after the image is built, before it’s pushed to a registry or deployed.

Full container and Kubernetes hardening guidance (base image selection, non-root users, read-only filesystems, admission control, runtime security) is covered in Container and Kubernetes Security; this note focuses on where image scanning sits in the pipeline and how it gates deployment.

Tool category comparison

CategoryQuestion it answersWhat it catchesRuns whenBlind spot
SAST”Is my code written safely?”Injection patterns, weak crypto, unsafe deserialization, hardcoded secrets in logicOn every commit/PR, fastest feedbackNo runtime context; false positives on safe-in-context code
DAST”Does my running app behave safely under attack?”Broken auth, injection reachable over HTTP, misconfigurations, OWASP Top 10Against a deployed instance (staging/review app), slowerNeeds a running target; limited code coverage vs. exercised paths only
SCA”Are my dependencies safe?”Known CVEs in direct/transitive dependencies, license violationsOn every build, and continuously as new CVEs are publishedDoesn’t catch vulnerabilities not yet in a CVE database (0-days), or misuse of a safe library
IaC scanning”Is my infrastructure defined safely?”Open security groups, public buckets, over-privileged IAM, missing encryptionOn every commit/PR touching IaC, fastDoesn’t validate actual deployed drift from the IaC definition
Secrets scanning”Did I commit a credential?”API keys, tokens, private keys, passwords in code or historyPre-commit and on every push, very fastCan miss secrets in unusual formats/entropy; doesn’t catch secrets injected only at runtime
Container image scanning”Is my built image safe to run?”Vulnerable OS/app packages in the image, root user, bad Dockerfile practicesAfter build, before push/deployDoesn’t catch application-logic vulnerabilities (that’s SAST/DAST’s job)

No single category replaces another — a mature pipeline runs all of them, each acting as a different layer of defense-in-depth applied specifically to the build/release process.

Key Concepts

A real GitHub Actions pipeline: build → scan → gate → deploy

The workflow below stitches SAST, SCA, secrets scanning, container scanning, SBOM generation, and artifact signing into a single pipeline, gating the deploy job on all scans passing. It uses OIDC to assume a cloud role instead of static credentials, and pins actions to keep the example realistic (shortened SHAs shown for readability — pin to full 40-character SHAs in practice).

name: build-scan-deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read        # least privilege by default; elevate per-job as needed
  id-token: write        # required for OIDC federation to cloud provider
  security-events: write  # required to upload SARIF to the Security tab

env:
  IMAGE_NAME: ghcr.io/${{ github.repository }}/app

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4  # pin to full commit SHA in production

      - name: Set up build tooling
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies (locked, no scripts)
        run: npm ci --ignore-scripts

      - name: Build image
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          outputs: type=docker,dest=/tmp/app-image.tar

      - name: Upload image artifact for downstream jobs
        uses: actions/upload-artifact@v4
        with:
          name: app-image
          path: /tmp/app-image.tar
          retention-days: 1

  sast:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript

      - name: Run CodeQL analysis
        uses: github/codeql-action/analyze@v3   # uploads SARIF automatically

      - name: Semgrep scan
        uses: semgrep/semgrep-action@v1
        with:
          config: p/ci  # curated default ruleset

  secrets-scan:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # full history so gitleaks can scan past commits too

      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITLEAKS_ENABLE_UPLOAD_ARTIFACT: true
        # fails the job (non-zero exit) if any secret is detected

  sca:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4

      - name: Dependency vulnerability scan (Trivy fs mode)
        uses: aquasecurity/trivy-action@0.24.0
        with:
          scan-type: fs
          scan-ref: .
          severity: CRITICAL,HIGH
          exit-code: "1"          # fail the build on critical/high findings
          format: sarif
          output: trivy-fs.sarif

      - name: Upload SCA results to Security tab
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-fs.sarif

  container-scan:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-image
          path: /tmp

      - name: Load image for scanning
        run: docker load --input /tmp/app-image.tar

      - name: Scan built image (Trivy)
        uses: aquasecurity/trivy-action@0.24.0
        with:
          image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: "1"
          format: sarif
          output: trivy-image.sarif
          input: /tmp/app-image.tar

      - name: Upload image scan results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-image.sarif

      - name: Generate SBOM (SPDX, via Syft)
        uses: anchore/sbom-action@v0
        with:
          image: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json

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

  # deploy only runs once every scanning gate above has passed
  deploy:
    needs: [sast, secrets-scan, sca, container-scan]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production   # environment protection rule = manual approval gate
    permissions:
      contents: read
      id-token: write          # OIDC token for cloud auth, no static keys
      packages: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-image
          path: /tmp

      - name: Load and push image
        run: |
          docker load --input /tmp/app-image.tar
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Install cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign image (keyless, via Sigstore/Fulcio + Rekor)
        run: cosign sign --yes ${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Attach SBOM attestation to image
        uses: actions/download-artifact@v4
        with:
          name: sbom
          path: .
      - run: cosign attest --yes --predicate sbom.spdx.json --type spdxjson ${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Assume cloud deploy role via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gh-actions-deploy
          aws-region: us-east-1

      - name: Deploy
        run: ./scripts/deploy.sh ${{ env.IMAGE_NAME }}:${{ github.sha }}

Key design choices worth calling out:

Supply chain security: SBOMs

A Software Bill of Materials (SBOM) is a structured, machine-readable inventory of everything that makes up a piece of software — direct and transitive dependencies, their versions, licenses, and origins. It answers the question “what is actually inside this artifact?”, which matters enormously when a new CVE is disclosed (e.g., Log4Shell): with an SBOM, you can grep/query for “who has log4j-core version X” across your entire fleet in minutes instead of manually auditing every service.

FormatMaintained byNotable strengths
SPDX (Software Package Data Exchange)Linux Foundation, ISO/IEC 5962:2021 standardStrong license-compliance metadata, mature tooling, ISO-standardized
CycloneDXOWASPSecurity-focused design, native support for vulnerability (VEX) and provenance data, lighter-weight

Both are widely supported by tools like Syft, Trivy, and language-native generators (e.g., cyclonedx-npm, cyclonedx-gomod). Generating an SBOM at build time and attaching it to the release (as a build artifact and/or a signed attestation, as in the pipeline above) lets downstream consumers — including your own future incident responders — answer “are we affected by CVE-X” without re-scanning from scratch.

Supply chain security: provenance, attestation, and SLSA

Provenance is verifiable metadata about how an artifact was built: from which source commit, by which build system, using which build steps, on which platform — as opposed to trusting an artifact just because it appeared in the right registry with the right name. An attestation is a signed statement asserting a fact about an artifact (e.g., “this SBOM belongs to this image digest,” or “this image was built by this specific CI workflow run from this specific commit”), typically encoded using the in-toto attestation format.

SLSA (Supply-chain Levels for Software Artifacts), pronounced “salsa,” is a framework (originally from Google, now under OpenSSF) that defines graduated levels of supply chain integrity:

SLSA levelRequirementWhat it defends against
Build L0No guarantees
Build L1Build process is scripted/automated and generates provenanceAccidental, undocumented, manual build steps
Build L2Provenance is generated by a hosted build service and is signedTampering with provenance after the fact
Build L3Build runs in a hardened, isolated environment; provenance is non-forgeable even by the build service’s own usersAn attacker with access to the build definition tampering with the build process itself

GitHub Actions can generate SLSA-compliant provenance using the slsa-framework/slsa-github-generator reusable workflow, which runs the actual build in an isolated, GitHub-controlled generator workflow that the calling repository cannot directly tamper with — satisfying the “non-forgeable” requirement of higher SLSA levels. Consumers can then verify provenance with slsa-verifier before trusting a downloaded artifact, checking that it really was built from the expected source repository and workflow, not just that an artifact with the right filename exists somewhere.

Supply chain security: signed artifacts with Sigstore

Sigstore is an open-source project (now part of OpenSSF) that makes artifact signing accessible without the traditional pain of long-lived private key management, via three components:

At deploy time, an admission controller (e.g., Kyverno or sigstore/policy-controller in Kubernetes) can enforce “only run images signed by our CI pipeline’s OIDC identity,” rejecting any image — even one with the right name and tag — that lacks a valid, verifiable signature from the expected source. This closes the gap between “we scanned it in CI” and “we can prove at deploy time that what’s running is what we scanned,” which is the actual security property supply chain tooling is trying to deliver — a scan result is worthless if nothing prevents deploying a different, unscanned image with the same tag.

Dependency pinning

Pinning means locking a dependency (library, base image, or CI action) to an exact, immutable version rather than a mutable reference that could resolve to something different tomorrow.

What to pinMutable (avoid)Immutable (prefer)
Application dependenciesLoose version ranges without a lockfile (^1.2.0 with no package-lock.json)Committed lockfile (package-lock.json, poetry.lock, go.sum) resolving to exact versions/hashes
Container base imagesFROM node:20 (tag can be repointed)FROM node:20@sha256:<digest> (content-addressed, immutable)
CI/CD reusable actionsuses: actions/checkout@v4uses: actions/checkout@<full-commit-sha>

Pinning trades a small amount of convenience (you must deliberately bump the pin) for a large amount of predictability and auditability: a build today and the “same” build next month use byte-for-byte identical inputs, and a compromised upstream tag/maintainer account can’t silently change what you consume. This is deliberately in tension with automated patching (below) — the resolution is to pin, but update the pin frequently and automatically via bot-opened PRs rather than leaving ranges unpinned.

Automated patching

Pinning everything would freeze a codebase in time if nothing then kept those pins current — so pinning and automated patch cadence are two halves of the same practice, not opposites.

Gating strategy: fail vs. warn, and avoiding scanner fatigue

Not every finding should block a build — a pipeline that fails on every low-severity linting-style finding trains developers to ignore or bypass it entirely (“scanner fatigue”), which defeats the purpose more thoroughly than not scanning at all. A workable gating strategy is risk-based and environment-aware:

SeverityOn a feature branch/PROn merge to mainBefore production deploy
Critical / actively exploitedFail the buildFail the buildFail the deploy, no override
HighFail the buildFail the buildFail the deploy; override requires a documented, time-boxed exception
MediumWarn / annotate PR, don’t blockWarn, track in backlog with an SLAWarn, but allow deploy
Low / informationalVisible in the Security tab onlyVisible onlyNot a gate

Practical measures to keep this sustainable:

Best Practices

References