Le Duy Khuong

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

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

Dependency Poisoning — Khi pip install Trở Thành Vũ Khí

TeamPCP tấn công 5 ecosystems cùng lúc: GitHub Actions, Docker Hub, npm, Open VSX, PyPI. Họ công khai nhận credit. Và nói sẽ có thêm.

2026-03-2614 phút đọcVI

Phần 4 của 757% hoàn thành

Dependency Poisoning — Khi pip install Trở Thành Vũ Khí

Tháng 3 năm 2025, một nhóm tự xưng TeamPCP thực hiện điều mà hầu hết attacker không bao giờ làm: họ công khai nhận credit. Không ẩn danh. Không che giấu dấu vết. Họ tấn công 5 ecosystems cùng lúc — GitHub Actions, Docker Hub, npm, Open VSX, PyPI — và đăng thông điệp: "Đây chỉ là khởi đầu."

Điều đáng chú ý không phải quy mô tấn công. Mà là phương pháp. Mỗi ecosystem bị tấn công bằng một vector khác nhau, nhưng tất cả đều khai thác cùng một điểm yếu: sự tin tưởng mặc định vào package registry.

Bạn chạy pip install package-name. pip kết nối tới PyPI, tải về package, thực thi setup.py. Không hỏi. Không xác nhận. Không sandbox. Một lệnh duy nhất, quyền thực thi code tùy ý trên máy bạn.

Câu hỏi không phải "nếu" dependencies của bạn bị nhắm tới. Mà là "khi nào".


1. Bốn Attack Vector Bạn Phải Biết

Typosquatting — Sai Một Ký Tự, Mất Toàn Bộ

Attacker đăng ký package với tên gần giống package phổ biến. requets thay vì requests. python-dateutils thay vì python-dateutil. pytorch-nightly thay vì torch.

Tấn công này hiệu quả vì não người xử lý text bằng pattern matching, không phải character-by-character comparison. Bạn đọc requets và thấy requests. IDE autocomplete cũng không giúp — nó suggest từ registry, và package giả nằm ngay trong registry.

Package giả thường hoạt động bình thường — nó chỉ wrap package thật rồi thêm một payload nhỏ. Exfiltrate environment variables. Ghi SSH key. Gửi hostname và IP về C2 server. Bạn không biết mình bị nhiễm vì mọi thứ vẫn chạy đúng.

TeamPCP dùng chính kỹ thuật này trên PyPI. Nhưng thay vì ẩn, họ đặt thông điệp ngay trong package description: "This is a proof of concept."

Dependency Confusion — Khi Internal Trùng Tên Public

Nếu tổ chức bạn có internal package tên analytics-core và không đăng ký tên đó trên PyPI, attacker có thể đăng ký analytics-core trên PyPI với version cao hơn. pip mặc định ưu tiên version cao nhất. Kết quả: build system của bạn pull package của attacker thay vì internal package.

Đây là attack vector mà Alex Birsan đã demonstrate năm 2021, ảnh hưởng tới Apple, Microsoft, và hàng chục công ty khác. Năm năm sau, nhiều tổ chức vẫn chưa cấu hình --index-url--extra-index-url đúng cách.

Maintainer Account Takeover — Chiếm Quyền Người Duy Trì

Hàng nghìn package phổ biến trên PyPI được maintain bởi một người duy nhất. Người đó dùng password yếu, không bật 2FA, hoặc reuse password từ một breach cũ. Attacker chiếm account, push version mới chứa malware, và hàng triệu downstream user tự động cập nhật.

Đây không phải giả thuyết. event-stream trên npm — 2 triệu download mỗi tuần — bị compromise chính xác bằng cách này năm 2018. Maintainer gốc chuyển quyền cho một người "muốn giúp maintain", người đó bổ sung dependency chứa malware nhắm vào Bitcoin wallet.

CI/CD Credential Theft — Đầu Độc Từ Pipeline

Quay lại vụ việc đã phân tích trong bài 1 của series: attacker compromise Trivy (security scanner), lấy được CI credentials, dùng credentials đó để push phiên bản bị nhiễm lên PyPI. Người dùng chạy pip install --upgrade và nhận malware.

Vector này đặc biệt nguy hiểm vì package bị nhiễm được sign và publish từ chính CI/CD pipeline hợp lệ. Không alert. Không anomaly. Chỉ là một phiên bản mới, từ cùng maintainer, qua cùng pipeline.

TeamPCP kết hợp nhiều vector trong một chiến dịch. GitHub Actions marketplace bị typosquatting. Docker Hub bị push image giả. npm và PyPI bị cả typosquatting lẫn dependency confusion. Open VSX — marketplace cho VS Code extensions — bị extensions giả mạo. Năm mặt trận, cùng lúc, từ cùng một nhóm.


2. Lockfiles, Version Specs, và Những Thứ Bạn Nghĩ An Toàn

Version Specifier Trong Python — Mỗi Ký Tự Có Nghĩa

# Nguy hiểm — chấp nhận mọi version từ 2.0.0 trở lên
pydantic>=2.0.0
 
# An toàn hơn — compatible release, chấp nhận 2.10.x nhưng không 2.11.0
pydantic~=2.10.0
 
# Nghiêm ngặt nhất — chính xác version này
pydantic==2.10.3

>= nghĩa là "từ đây trở lên, không giới hạn". Nếu attacker push version 99.0.0, pip sẽ cài nó. ~= giới hạn trong phạm vi compatible (tương đương >=2.10.0, <2.11.0). == pin chính xác — an toàn nhất nhưng cần cập nhật thủ công.

Trong production, == hoặc ~= là lựa chọn hợp lý. >= trong requirements.txt production là một lỗ hổng đang chờ bị khai thác.

Lockfile — Bản Chụp Của Dependency Graph

Lockfile (package-lock.json, pnpm-lock.yaml, poetry.lock, uv.lock) ghi lại chính xác phiên bản nào đã được resolve, bao gồm cả transitive dependencies. Nó là snapshot tại thời điểm lock — không thay đổi trừ khi bạn chủ động update.

Lockfile bảo vệ bạn khỏi điều gì?

  • Package mới bị push lên registry giữa hai lần build → lockfile vẫn trỏ tới version cũ
  • Transitive dependency bị hijack → lockfile đã ghi hash, mismatch sẽ fail build
  • Version mới có breaking change → lockfile giữ nguyên version đã test

Lockfile không bảo vệ bạn khỏi điều gì?

  • Package bị compromise trước khi bạn lock → bạn đã lock version nhiễm
  • pip install (không có lockfile) trong Dockerfile → mỗi build có thể khác nhau
  • Ai đó chạy npm install thay vì npm ci trong CI → lockfile bị bỏ qua

pip-audit, npm audit, và Trivy

Ba công cụ, ba ecosystem, cùng mục đích: quét dependencies và so sánh với cơ sở dữ liệu lỗ hổng đã biết.

pip-audit kiểm tra Python packages dựa trên database của PyPI Advisory và OSV. npm audit dùng GitHub Advisory Database. Trivy quét cả container images, filesystem, và IaC configs.

Nhưng lưu ý: những công cụ này chỉ phát hiện lỗ hổng đã biết. Nếu attacker push package mới chưa bị report, không tool nào phát hiện được. Chúng là lớp phòng thủ cần thiết nhưng không đủ.

SBOM — Biết Chính Xác Bạn Đang Chạy Gì

Software Bill of Materials (SBOM) là danh sách đầy đủ mọi component trong phần mềm: tên, version, nguồn gốc, license. Nó không ngăn tấn công, nhưng khi sự cố xảy ra, bạn biết ngay mình có bị ảnh hưởng không.

Khi tin tức về LiteLLM compromise phát ra, câu hỏi đầu tiên mọi team hỏi: "Mình có dùng package này không? Version nào?" Nếu có SBOM, câu trả lời là 30 giây. Không có SBOM, câu trả lời là "chúng tôi đang kiểm tra" — và trong thời gian đó, malware đã chạy.

Transitive Dependencies — Tảng Băng Chìm

Bạn khai báo 10 dependencies trong requirements.txt. Nhưng mỗi dependency lại phụ thuộc vào 5-15 package khác. Và mỗi package đó lại có dependencies riêng. Kết quả: 10 direct dependencies có thể kéo theo 200+ transitive dependencies.

Bạn audit 10 package mình khai báo. Nhưng attacker nhắm vào package thứ 187 trong dependency tree — cái mà bạn chưa bao giờ nghe tên. Đó là lý do lockfile và SBOM tồn tại: chúng làm visible phần chìm của tảng băng.


3. Code Thực Tế — Phát Hiện và Ngăn Chặn

Pre-commit Hook: Phát Hiện Floating Versions

#!/bin/bash
# .git/hooks/pre-commit hoặc dùng pre-commit framework
# Cảnh báo khi requirements.txt chứa floating version
 
EXITCODE=0
 
for file in $(git diff --cached --name-only | grep -E 'requirements.*\.txt$'); do
  FLOATING=$(grep -nE '^[a-zA-Z].*>=[0-9]' "$file" | grep -v ',' | grep -v '~=')
  if [ -n "$FLOATING" ]; then
    echo "WARNING: Floating versions detected in $file:"
    echo "$FLOATING"
    echo "Consider using ~= (compatible release) or == (exact pin)"
    EXITCODE=1
  fi
done
 
exit $EXITCODE

Logic: grep tìm dòng có >= nhưng không có dấu , (đã có upper bound) và không phải ~=. Nếu tìm thấy — cảnh báo. Đơn giản, chạy nhanh, chặn được lỗi phổ biến nhất.

CI Workflow: Automated Dependency Audit

# .github/workflows/dependency-audit.yml
name: Dependency Audit
on:
  schedule:
    - cron: '0 8 * * 1'  # Mỗi thứ Hai lúc 8:00 UTC
  pull_request:
    paths:
      - 'requirements*.txt'
      - 'pyproject.toml'
      - 'package-lock.json'
      - 'pnpm-lock.yaml'
 
jobs:
  python-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install pip-audit
      - run: pip-audit -r requirements.txt --desc on --fix --dry-run
 
  npm-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm audit --audit-level=moderate

Hai điểm quan trọng:

  1. Chạy định kỳ (cron): Lỗ hổng mới được report mỗi ngày. Package an toàn hôm nay có thể bị CVE ngày mai. Scan weekly bắt kịp.
  2. Chạy khi dependency files thay đổi: PR thêm dependency mới → audit ngay, không đợi weekly scan.

Đọc Output npm audit

$ npm audit
 
# npm audit report
 
axios  >=0.8.1 <1.7.4
  Severity: high
  Server-Side Request Forgery - https://github.com/advisories/GHSA-xxx
  fix available via `npm audit fix`
 
3 vulnerabilities (1 low, 1 moderate, 1 high)

Ba thông tin cần: tên package, severity, và liệu có fix available. npm audit fix tự update lên version an toàn trong phạm vi semver range. npm audit fix --force cho phép breaking changes — cẩn thận, test lại sau khi chạy.

Python Pinning Đúng Cách

# requirements.txt — production
pydantic==2.10.3
httpx==0.28.1
uvicorn==0.34.0
 
# requirements-dev.txt — development (nới lỏng hơn, chấp nhận được)
pydantic~=2.10.0
httpx~=0.28.0
pytest>=8.0,<9.0

Production pin chính xác. Development dùng compatible release. Logic: production cần reproducibility, development cần flexibility để test với minor updates.


4. Quy Trình Audit Dependency — Từ Reactive Sang Proactive

Bước 1: Automated Weekly Scan

CI cron job chạy pip-auditnpm audit mỗi tuần. Kết quả gửi về Slack hoặc email. Không cần human action trừ khi có finding.

Bước 2: PR Review — Dependency Files Là Critical Path

Khi PR thay đổi requirements.txt, package.json, pyproject.toml, hoặc lockfile — đây là thay đổi cần review kỹ hơn code thông thường. Câu hỏi cho reviewer:

  • Package mới hay update version?
  • Nếu package mới: maintainer là ai? Bao nhiêu download? Commit cuối khi nào? Có 2FA enforce không?
  • Nếu update: changelog có gì? Breaking changes?
  • Lockfile có được regenerate đúng cách không?

Bước 3: Pre-commit — Chặn Floating Versions

Hook ở section 3 chạy trước mỗi commit. Developer nhận warning ngay, sửa trước khi push. Rẻ hơn nhiều so với phát hiện ở CI.

Bước 4: Lockfile Trong CI — npm ci, Không Phải npm install

# Sai — npm install có thể modify lockfile
npm install
 
# Đúng — npm ci đọc lockfile, fail nếu mismatch
npm ci

npm ci đảm bảo CI build sử dụng chính xác dependencies đã lock. Nếu package.jsonpackage-lock.json không khớp, build fail. Đây là behavior bạn muốn — fail loud, fail fast.

Python chưa có tương đương native, nhưng pip install --require-hashes -r requirements.txt gần nhất: mỗi package phải match hash đã ghi trong requirements file.

Bước 5: Đánh Giá Dependency Mới

Trước khi thêm dependency mới, checklist:

  • Maintainer có uy tín? (profile, lịch sử contribute, tổ chức backing)
  • Download count hợp lý? (package 10 download/tuần mà claim thay thế requests? Red flag)
  • Commit gần nhất khi nào? (Package abandoned 2 năm = rủi ro unpatched CVEs)
  • License compatible?
  • Có bao nhiêu transitive dependencies? (Thêm 1 package kéo theo 50 deps = 50 thêm trust decisions)
  • Có alternative nhẹ hơn không? (Stdlib đủ dùng thì không cần third-party)

Bước 6: Incident Response — Khi Vulnerability Xuất Hiện

  1. Xác định exposure: Package nào bị ảnh hưởng? Version nào? Có trong production không?
  2. Đánh giá severity: CVE score bao nhiêu? Có exploit in the wild? Có ảnh hưởng tới use case cụ thể của mình?
  3. Pin safe version: Update requirements file, regenerate lockfile, test
  4. Audit transitive deps: Vulnerability có nằm ở direct dep hay transitive? Nếu transitive — direct dep đã fix chưa?
  5. Deploy và verify: Push fix, monitor logs, confirm không regression
  6. Post-mortem: Tại sao không phát hiện sớm hơn? Weekly scan có chạy không? Alert có tới đúng người không?

5. AI Tools — Dependency Graph Phức Tạp Nhất

AI frameworks có đặc thù khiến vấn đề dependency nghiêm trọng hơn bất kỳ domain nào khác.

Con Số Thực Tế

Cài LangChain kéo theo khoảng 200 transitive dependencies. Một AI gateway phổ biến kéo theo 50+. Một vector database embedded kéo theo 80+. Mỗi dependency là một trust decision mà bạn không chủ động đưa ra.

So sánh: một web framework điển hình như Flask có khoảng 10 direct dependencies. Django khoảng 5. Sự khác biệt không phải 2x hay 3x — mà là bậc số mũ.

Áp Lực Cập Nhật Liên Tục

AI tools release nhanh. Model mới hỗ trợ, provider mới thêm, API thay đổi. Pressure để pip install --upgrade mạnh hơn bất kỳ ecosystem nào khác. Và mỗi lần upgrade mà không audit — bạn mở rộng attack surface.

"Tôi cần version mới vì nó hỗ trợ Claude 4" — đúng, nhưng version mới đó cũng update 30 transitive dependencies mà bạn không kiểm tra.

MCP Plugins — Thêm Một Lớp Unaudited

Model Context Protocol (MCP) plugins cho phép LLM gọi tools bên ngoài. Mỗi plugin là một package riêng, với dependency tree riêng, từ maintainer riêng. Bạn cài 10 MCP plugins — bạn thêm 10 dependency trees mà không ai audit.

Và MCP plugins có quyền hạn đặc biệt: chúng thường access file system, network, API keys. Một plugin bị compromise không chỉ leak data của chính nó — mà mọi thứ mà MCP host process có quyền truy cập.

Recommendations Thực Tế

Tách biệt môi trường: Mỗi AI tool chạy trong virtual environment riêng (Python venv) hoặc container riêng. Tool A bị compromise không lan sang Tool B.

# Tạo venv riêng cho từng tool
python -m venv ~/.venvs/litellm
python -m venv ~/.venvs/langchain
python -m venv ~/.venvs/chromadb

Pin everything: Không ngoại lệ. Dùng pip freeze > requirements.txt sau khi test, commit file đó.

Audit weekly: Cron job chạy pip-audit trong mỗi venv. Kết quả aggregate vào một dashboard hoặc notification channel.

SBOM cho mỗi deployment: Khi deploy AI tool mới, generate SBOM. Khi tin tức về vulnerability phát ra, grep SBOM thay vì panic.

# Generate SBOM với pip-audit (CycloneDX format)
pip-audit --format cyclonedx-json -o sbom.json -r requirements.txt

Review MCP plugins như review code: Đọc source. Kiểm tra dependencies. Chạy trong sandbox trước khi cho access production secrets.


Tin Tưởng Nhưng Phải Xác Minh

pip installnpm install là hai lệnh mà developer chạy hàng chục lần mỗi ngày mà không suy nghĩ. TeamPCP đã chỉ ra rằng sự tin tưởng đó có thể bị khai thác — ở quy mô lớn, cùng lúc, trên nhiều ecosystems.

Phòng thủ không phải ngừng cài package. Mà là thay đổi mặc định: từ "cài rồi tính" sang "kiểm tra rồi cài". Pin version. Lock dependencies. Audit weekly. Review PR thay đổi dependency files như review PR thay đổi authentication logic.

Dependency graph của bạn là trust graph. Mỗi package bạn thêm vào là một quyết định tin tưởng mà bạn cần đưa ra có chủ đích — không phải mặc định.


Đây là bài 4/7 trong series Bảo Mật Trong Kỷ Nguyên AI Agent.

Bài tiếp trong series: Security Tools Là Attack Surface — Nghịch Lý Của Defense-in-Depth.

LDK

Le Duy Khuong

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