Le Duy Khuong

Chuỗi: ai-security-supply-chain · Phần 5

Năng suất & công cụ dev

Security Tools Là Attack Surface — Nghịch Lý Của Defense-in-Depth

Trivy — security scanner được triệu người tin dùng — bị compromise và trở thành cánh cửa vào LiteLLM. Công cụ chúng ta tin tưởng nhất có quyền access cao nhất. Khi chúng sập, blast radius khổng lồ.

2026-03-2613 phút đọcVI

Phần 5 của 771% hoàn thành

Security Tools Là Attack Surface — Nghịch Lý Của Defense-in-Depth

Tháng 12 năm 2020, thế giới phát hiện một trong những vụ tấn công tinh vi nhất lịch sử ngành bảo mật. Attacker không tấn công trực tiếp mục tiêu. Họ cài backdoor vào build process của một monitoring tool — SolarWinds Orion. 18.000 tổ chức cài bản cập nhật chứa malware. Bộ Tài chính Mỹ, Bộ An ninh Nội địa, Microsoft, FireEye — đều là nạn nhân. Backdoor tồn tại nhiều tháng trước khi bị phát hiện.

Bốn năm sau, pattern này không biến mất. Nó lặp lại — mỗi lần tinh vi hơn, mỗi lần nhắm vào một mắt xích khác trong chuỗi tin cậy. Codecov (2021), CircleCI (2023), ua-parser-js (2021), và gần nhất là Trivy dẫn tới LiteLLM (2026). Vector tấn công luôn giống nhau: đánh vào thứ mà target đặt niềm tin cao nhất.

Và đây là nghịch lý: security tools — thứ chúng ta triển khai để bảo vệ hạ tầng — lại có quyền access cao nhất trong toàn bộ pipeline. Khi chúng bị compromise, blast radius lớn hơn bất kỳ ứng dụng nào khác.


1. Lịch Sử Lặp Lại — Từ SolarWinds Đến Trivy

Nếu nhìn lại timeline các vụ supply chain lớn, bạn sẽ thấy một pattern rõ ràng đến đáng sợ.

SolarWinds Orion (2020) — Attacker truy cập vào build system, chèn mã độc vào quá trình biên dịch. Bản cập nhật chính thức của SolarWinds phân phối malware SUNBURST. 18.000 tổ chức cài đặt. Phát hiện sau 9 tháng. Điểm đặc biệt: không có artifact nào trên repository bị thay đổi — malware được inject trong quá trình build, nên source code review sẽ không thấy gì.

Codecov (2021) — Bash uploader script bị sửa đổi. Mỗi lần CI chạy, script gửi environment variables (bao gồm credentials, tokens, API keys) về server của attacker. Hai tháng trước khi bị phát hiện. Hàng nghìn repositories bị lộ secrets.

ua-parser-js (2021) — npm package với 8 triệu lượt download mỗi tuần. Tài khoản maintainer bị chiếm đoạt. Phiên bản chứa cryptominer và credential stealer được publish lên npm. Mọi pipeline có npm install trong thời gian đó đều bị ảnh hưởng.

CircleCI (2023) — Credential của một nhân viên bị đánh cắp qua malware trên laptop cá nhân. Attacker truy cập được production environment, và quan trọng hơn: tất cả secrets mà khách hàng lưu trữ trong CircleCI. Mọi token, mọi API key, mọi deployment credential.

Trivy → LiteLLM (2026) — Security scanner bị compromise. CI credentials của Trivy bị đánh cắp, dùng để push phiên bản LiteLLM chứa malware lên PyPI. Security scanner — công cụ được thiết kế để phát hiện mã độc — trở thành phương tiện phân phối mã độc.

Mỗi vụ đều có chung một đặc điểm: attacker không tấn công mục tiêu cuối cùng. Họ tấn công công cụ mà mục tiêu tin tưởng. Và lý do đơn giản: công cụ đó đã có quyền truy cập sẵn.

CI/CD pipeline là "crown jewels" trong bất kỳ tổ chức nào. Nó có quyền đọc source code, truy cập secrets, build và deploy artifacts, push lên registries, tương tác với production. Ai kiểm soát pipeline, người đó kiểm soát mọi thứ.


2. Trust Chains, OIDC, và Nguyên Tắc Least Privilege

Vấn đề cốt lõi không phải là "security tools có lỗi". Vấn đề là mô hình trust mà chúng ta đang dùng cho CI/CD đã lỗi thời.

Long-lived tokens: quả bom hẹn giờ

Cách truyền thống để CI/CD truy cập cloud resources: tạo một service account, generate API key hoặc access token, lưu vào CI secrets. Token đó sống mãi (hoặc cho đến khi ai đó nhớ rotate nó — thường là không bao giờ).

Vấn đề:

  • Token bị leak một lần = access vĩnh viễn cho đến khi bị revoke
  • Không có context: token không biết nó đang được dùng trong pipeline nào, branch nào, job nào
  • Blast radius rộng: một token thường có quyền truy cập nhiều hơn mức cần thiết
  • Rotation thực tế hiếm khi xảy ra, đặc biệt trong team nhỏ

CircleCI là ví dụ hoàn hảo. Khi attacker truy cập được kho secrets, họ có mọi thứ — không phải vì CircleCI bảo mật kém, mà vì mô hình long-lived tokens nghĩa là mỗi secret là một "master key" không hết hạn.

OIDC: short-lived, scoped, verifiable

OpenID Connect (OIDC) cho CI/CD hoạt động khác hoàn toàn. Thay vì lưu token, pipeline yêu cầu một JWT token ngắn hạn từ CI provider, rồi exchange JWT đó với cloud provider để lấy credentials tạm thời.

Luồng hoạt động:

  1. GitHub Actions job bắt đầu → GitHub phát hành JWT với thông tin: repo, branch, workflow, actor
  2. Job gửi JWT tới AWS/GCP/Azure → Cloud provider verify signature
  3. Cloud provider kiểm tra claims (đúng repo? đúng branch? đúng workflow?)
  4. Nếu hợp lệ → cấp temporary credentials (15-60 phút)
  5. Job kết thúc → credentials tự hết hạn

Lợi ích: không có secret nào được lưu trong CI. Không có gì để leak. Credentials tự hết hạn. Mỗi job nhận đúng quyền nó cần, không hơn.

GitHub Actions: permissions là hàng rào đầu tiên

Mặc định, GITHUB_TOKEN trong GitHub Actions có quyền rộng hơn mức cần thiết. Và nhiều workflow không khai báo permissions — nghĩa là chạy với default (thường là read-write cho mọi scope).

Nguyên tắc: khai báo permissions tối thiểu cho mỗi workflow. Nếu workflow chỉ cần đọc code và chạy tests, nó không cần quyền ghi packages hay deploy.

Pin actions bằng SHA, không bằng tag

actions/checkout@v4 trông có vẻ ổn. Nhưng v4 là một git tag — mutable, có thể bị ghi đè. Nếu tài khoản maintainer bị compromise, attacker có thể push mã độc và gắn lại tag v4.

Pin bằng full commit SHA: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11. SHA là immutable — bạn biết chính xác code nào đang chạy trong pipeline.

Supply chain attestation: SLSA và Sigstore

SLSA (Supply-chain Levels for Software Artifacts) là framework định nghĩa các mức độ bảo đảm cho supply chain. Từ Level 1 (có build provenance) đến Level 4 (hermetic, reproducible builds). Sigstore cung cấp công cụ ký và verify: cosign cho container images, rekor cho transparency log.

Ý tưởng cốt lõi: mỗi artifact phải đi kèm bằng chứng về nguồn gốc — ai build, trên hệ thống nào, từ source code nào. Consumer verify attestation trước khi sử dụng.


3. Cấu Hình Thực Tế — Hardening CI/CD

Lý thuyết xong, đây là cấu hình cụ thể bạn có thể áp dụng ngay.

GitHub Actions: permissions tối thiểu

name: Build and Test
on: [push, pull_request]
 
# Workflow-level: restrict mọi job
permissions:
  contents: read
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # Pin bằng full SHA — không dùng @v4
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b
        with:
          node-version: "20"
      - run: npm ci
      - run: npm test

Mỗi action được pin bằng commit SHA. permissions: contents: read đảm bảo workflow chỉ có thể đọc repository, không thể ghi, tạo release, hay push package.

OIDC thay thế long-lived secrets

name: Deploy to AWS
on:
  push:
    branches: [main]
 
permissions:
  id-token: write   # Cần thiết cho OIDC token
  contents: read
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
 
      - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-deploy
          aws-region: ap-southeast-1
          # Không có AWS_ACCESS_KEY_ID
          # Không có AWS_SECRET_ACCESS_KEY
          # Credentials được tạo tạm thời qua OIDC
 
      - run: aws s3 sync ./dist s3://my-bucket

Không có secret nào trong CI. IAM role github-deploy được cấu hình trust GitHub OIDC provider, chỉ chấp nhận requests từ đúng repo và đúng branch.

Docker credentials: bỏ plaintext

Nhiều developer có ~/.docker/config.json chứa registry credentials dạng plaintext. Đây là file đầu tiên malware tìm kiếm.

# Cấu hình credential helper thay vì lưu plaintext
# macOS — dùng Keychain
cat > ~/.docker/config.json << 'EOF'
{
  "credsStore": "osxkeychain"
}
EOF
 
# Linux — dùng pass (GPG-encrypted)
cat > ~/.docker/config.json << 'EOF'
{
  "credsStore": "pass"
}
EOF
 
# Verify: không có "auth" field trong config
cat ~/.docker/config.json | grep -c "auth"
# Output: 0 ← đúng. Nếu > 0, credentials vẫn đang lưu plaintext

Credential helper lưu trữ tokens trong hệ thống mã hoá của OS thay vì file text. Khi Docker cần authenticate, nó gọi helper, helper trả về token từ keychain/pass, không bao giờ lưu trên disk dạng đọc được.

Audit third-party actions

Trước khi dùng bất kỳ GitHub Action nào từ marketplace:

# Kiểm tra source repository
gh repo view owner/action-name --json name,owner,stargazerCount,updatedAt
 
# Đọc source code — action là code chạy trong pipeline CỦA BẠN
gh repo clone owner/action-name /tmp/action-review
# Review: action.yml, entrypoint, network calls, env var access
 
# Kiểm tra có verified publisher hay không
# Trên marketplace: tìm badge "Verified creator"

Một action có 10.000 stars không có nghĩa là an toàn. SolarWinds có hàng chục nghìn khách hàng enterprise. Popularity không phải security signal.


4. Quy Trình Audit CI/CD — Từ Reactive Sang Proactive

Phần lớn team chỉ nghĩ đến CI/CD security sau khi đã bị ảnh hưởng bởi một incident. Đây là quy trình audit bạn có thể chạy trước khi sự cố xảy ra.

Bước 1: Inventory secrets

Liệt kê tất cả secrets trong CI/CD. Với mỗi secret, trả lời: nó truy cập được gì? Tạo khi nào? Rotate lần cuối khi nào? Ai có quyền xem?

Phân loại theo blast radius:

  • Critical: deploy credentials, cloud admin, database production
  • High: registry push, package publish, DNS management
  • Medium: monitoring, logging, notification services
  • Low: test environment, sandbox access

Bước 2: Thay thế long-lived tokens bằng OIDC

Với mỗi secret ở mức Critical và High: cloud provider có hỗ trợ OIDC không? AWS, GCP, Azure đều hỗ trợ. Nhiều SaaS providers cũng đã hỗ trợ. Mỗi token thay thế bằng OIDC là một secret bạn không cần quản lý, không cần rotate, không thể bị leak.

Bước 3: Pin tất cả actions

Chạy qua mọi workflow file trong .github/workflows/. Mỗi uses: statement dùng tag → thay bằng full commit SHA. Ghi chú tag gốc trong comment để dễ update.

# Trước
- uses: actions/checkout@v4
# Sau
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.7

Bước 4: Set permissions tối thiểu

Thêm permissions: block vào mọi workflow. Bắt đầu với permissions: {} (không quyền gì) rồi thêm từng quyền cần thiết cho đến khi workflow pass. Cách này đảm bảo bạn biết chính xác mỗi workflow cần quyền gì.

Bước 5: Bật security features

GitHub cung cấp sẵn: Secret Scanning (phát hiện credentials bị commit), Dependabot (cảnh báo dependency có CVE), Code Scanning (static analysis). Đây là những layer miễn phí mà nhiều repository vẫn chưa bật.

Bước 6: Lịch rotate

Secrets không thể chuyển sang OIDC → đặt lịch rotate. Quarterly cho critical, semi-annual cho medium. Ghi lại ngày tạo và ngày rotate tiếp theo. Tự động hoá nếu có thể — manual rotation là rotation sẽ không xảy ra.

Bước 7: Review định kỳ

Mỗi quý, chạy lại từ bước 1. Secrets mới sẽ được thêm. Actions mới sẽ được dùng. Permissions sẽ bị mở rộng "tạm thời" rồi quên không thu hẹp lại. Audit là quá trình liên tục, không phải sự kiện một lần.


5. AI Agents — Khi Attack Surface Nhân Lên

Tất cả những gì thảo luận ở trên áp dụng cho CI/CD truyền thống — nơi pipeline được viết bởi developer và chạy deterministic. Khi AI agents tham gia, mô hình thay đổi hoàn toàn.

Agent chạy tools tự động

Một AI agent hiện đại không chỉ generate text. Nó gọi functions, chạy code, tương tác với API, đọc ghi filesystem, và kết nối với external services qua MCP servers hoặc function calling. Mỗi tool mà agent gọi được là một attack surface.

Nghĩ về điều này: khi bạn cho agent quyền gọi một MCP server, bạn đang implicit trust rằng server đó an toàn, output của nó đáng tin cậy, và agent sẽ xử lý output đúng cách. Ba giả định. Ba điểm có thể bị khai thác.

Prompt injection × tool calling

Prompt injection không chỉ là "nói agent làm điều sai". Khi agent có quyền gọi tools, prompt injection trở thành remote code execution. Attacker inject prompt vào data mà agent đọc (email, document, API response), prompt đó chỉ thị agent gọi tool nguy hiểm — và agent thực thi vì nó "tin" input đó là hợp lệ.

Ví dụ thực tế: agent đọc một pull request description chứa injection. Description chỉ thị agent approve PR và trigger deploy. Agent có quyền approve (vì nó cần quyền đó cho workflow bình thường). Kết quả: mã độc được deploy vào production bởi chính AI reviewer.

OWASP LLM Top 10 trong context CI/CD

Hai mục đáng chú ý nhất:

LLM01 — Prompt Injection: Input từ bên ngoài (code comments, PR descriptions, issue titles, log messages) có thể chứa instructions mà agent thực thi. Nếu agent có quyền trong CI/CD, injection trở thành pipeline manipulation.

LLM05 — Supply Chain Vulnerabilities: Agent dùng plugins, MCP servers, function schemas từ bên ngoài. Mỗi dependency là một trust relationship. Plugin bị compromise = agent bị compromise.

Sandboxing agent execution

Nguyên tắc giống CI/CD, nhưng nghiêm ngặt hơn vì agent behavior không deterministic:

  • Exec deny: Agent không được thực thi arbitrary commands. Whitelist cụ thể từng tool, từng command.
  • Network restrictions: Agent chỉ được gọi domains đã khai báo. Không cho phép outbound tới arbitrary endpoints.
  • Tool whitelists: Khai báo rõ ràng agent được gọi tools nào. Mọi thứ ngoài whitelist bị từ chối mặc định.
  • Output validation: Kết quả từ tool phải được validate trước khi agent sử dụng. Đừng trust raw output.
  • Read-only default: Agent chỉ có quyền đọc trừ khi write được approve rõ ràng cho task cụ thể.

Trust boundary dịch chuyển

Trong CI/CD truyền thống, trust boundary rõ ràng: developer viết code, pipeline chạy theo script, output deterministic. Bạn trust developer và trust pipeline logic.

Với AI agents, trust boundary dịch chuyển: bạn không chỉ trust code agent chạy, mà trust judgment của agent — quyết định nào nó sẽ đưa ra, tool nào nó sẽ gọi, data nào nó sẽ tin. Đây là bài toán khó hơn fundamentally, vì judgment không thể audit bằng static analysis.

Cách tiếp cận: treat agent như untrusted user với limited privileges. Cho nó đủ quyền để hoàn thành task, nhưng không hơn. Mọi hành động có side effect (write, deploy, approve) đều cần human approval hoặc policy gate. Đây không phải "AI không đáng tin" — đây là defense-in-depth áp dụng cho mọi thành phần, kể cả thành phần thông minh.


Bài Tiếp Theo

Security tools có quyền access cao vì chúng cần quyền đó để hoạt động. CI/CD pipelines nắm giữ mọi secret vì chúng cần secrets đó để deploy. Vấn đề không phải "bỏ quyền access" — mà là giới hạn scope, giới hạn thời gian, và verify liên tục.

Nhưng còn một lớp nữa thường bị bỏ qua: credentials — API keys, database passwords, tokens — thứ mà pipeline truyền thống lưu trong .env file và hy vọng không ai commit lên git. Bài tiếp: Credential Management — Vượt Ra Ngoài .env.


Đây là bài 5/7 trong series "Bảo Mật Trong Kỷ Nguyên AI Agent" — từ một sự cố thực tế đến toàn cảnh bảo mật khi AI agents trở thành infrastructure core.

LDK

Le Duy Khuong

AI Transformation & Digital Strategy. Writing about agentic systems, engineering leadership, and building in public.