3 điểm bởi GN⁺ 2025-12-19 | 1 bình luận | Chia sẻ qua WhatsApp
  • Seattle Times đã vô tình tránh được cuộc tấn công Shai-Hulud 2.0, nhưng với tiền đề rằng không thể coi may mắn là một chiến lược bảo mật, họ đã triển khai các biện pháp phòng vệ phía client
  • Những cải tiến của npm như Trusted publishing / provenance / granular tokens củng cố khía cạnh “phát hành”, nhưng vẫn để lại khoảng trống là không ngăn được việc thực thi mã độc tại thời điểm “cài đặt·cập nhật”
  • pnpm vẫn sử dụng npm registry như cũ nhưng bổ sung các cơ chế kiểm soát khiến việc chạy gói độc hại ở giai đoạn tiêu thụ (install/update) trở nên khó hơn
  • Trong giai đoạn thí điểm, họ áp dụng 3 cơ chế kiểm soát của pnpm để lần lượt chặn các vector như thực thi lifecycle scripts, cài ngay bản phát hành mới nhất, hạ mức độ tin cậy
  • Họ coi ngoại lệ không phải là thất bại mà là một phần của thiết kế, hướng tới vận hành theo defense-in-depth để vừa tài liệu hóa ngoại lệ vừa để các lớp còn lại tiếp tục bảo vệ

Bối cảnh sự cố và các giả định

  • Vào tháng 11 năm 2025, đã xảy ra một vụ worm npm tự sao chép lây nhiễm 796 package và lan rộng trên quy mô 132 triệu lượt tải mỗi tháng
  • Cuộc tấn công lợi dụng preinstall script để đánh cắp thông tin xác thực, cài backdoor duy trì truy cập, thậm chí trong một số môi trường còn xóa cả môi trường phát triển
  • Lý do tổ chức của họ không bị ảnh hưởng không phải vì phòng thủ mạnh, mà đơn giản là trùng hợp không chạy npm install/npm update trong thời gian bị tấn công
  • Với tổ chức tin tức, niềm tin là cốt lõi; một vụ xâm phạm chuỗi cung ứng có thể làm lộ dữ liệu khách hàng, thông tin xác thực của nhân viên, hạ tầng production và mã nguồn, đồng thời kéo theo chi phí khôi phục và thông báo rất lớn

Nhóm và bối cảnh triển khai

  • Seattle Times đã dùng npm làm package manager mặc định trong thời gian dài; từng thử nghiệm Yarn nhưng không gắn bó lâu dài
  • Lý do họ áp dụng pnpm là vì các cơ chế kiểm soát bảo mật phía client bổ sung cho các cải tiến ở cấp registry
  • pnpm được xem là drop-in replacement vì dùng cùng registry, cùng lệnh, cùng workflow, nên khả năng chuyển đổi được đánh giá là cao
  • Đây không phải một case study đã hoàn chỉnh, mà là chia sẻ về các vấn đề và quá trình suy nghĩ của một đội ngũ thực tế khi mới bắt đầu làm bảo mật chuỗi cung ứng

Vì sao cần cơ chế kiểm soát phía client

  • Các cải tiến bảo mật của npm đúng là đã khiến việc phát hành package độc hại sau khi chiếm đoạt tài khoản trở nên khó hơn
  • Tuy nhiên các cải tiến này bảo vệ phía “phát hành (publishing)”, chứ không ngăn việc tự thân cài package độc hại ở giai đoạn “tiêu thụ (consuming)”
  • Khi chạy npm install/npm update, lifecycle scripts (preinstall/postinstall, v.v.) có thể thực thi mã tùy ý với quyền của developer trước cả khi package được đánh giá là an toàn
  • Các script này có thể truy cập thông tin xác thực npm/GitHub/AWS/DB, mã nguồn, hạ tầng cloud và toàn bộ filesystem
  • Các cuộc tấn công như Shai-Hulud lợi dụng chính cấu trúc này; nếu tài khoản maintainer bị chiếm đoạt, script độc hại sẽ chạy ngay lúc cài đặt, gây thiệt hại trước khi cộng đồng kịp phát hiện
  • Họ kết hợp các cải tiến phía phát hành của npm và các cơ chế kiểm soát phía tiêu thụ của pnpm thành một lớp phòng thủ bổ trợ lẫn nhau, theo mô hình “defense-in-depth”

3 lớp đã áp dụng

  • Trong giai đoạn thí điểm, họ dùng đồng thời 3 cơ chế kiểm soát nhắm vào các vector tấn công khác nhau
  • Mỗi cơ chế đều có lối thoát cho các ngoại lệ thực tế, vì thiết kế được xây dựng trên giả định rằng trong môi trường thật sẽ luôn có ngoại lệ cần xử lý

Control 1: Quản lý Lifecycle Script

  • pnpm mặc định chặn lifecycle scripts và vẫn có thể tiếp tục cài đặt kèm cảnh báo
  • Do lo ngại cảnh báo có thể bị bỏ qua, họ chọn strictDepBuilds: true để buộc cài đặt thất bại ngay lập tức nếu phát hiện script
  • Ví dụ cấu hình trong pnpm-workspace.yaml gồm các trường sau
    • strictDepBuilds: true
    • onlyBuiltDependencies: danh sách cho phép các package có build script cần thiết
    • ignoredBuiltDependencies: danh sách chặn (hoặc bỏ qua) các package có build script không cần thiết
  • “Script cần thiết” được định nghĩa là các thao tác như biên dịch native extension hoặc liên kết thư viện phụ thuộc nền tảng
  • “Script không cần thiết” là các tối ưu hóa hay cấu hình tùy chọn, trong đó với cách dùng của nhóm thì không ảnh hưởng chức năng
  • Việc khiến cài đặt thất bại buộc nhóm phải thực hiện các bước tiếp theo
    • pnpm chỉ rõ package nào có script
    • Điều tra và hiểu rõ script đó làm gì
    • Có chủ đích quyết định cho phép/chặn bằng đánh giá của con người và ghi lại bằng tài liệu
  • Đội ngũ pnpm đang cân nhắc đưa strictDepBuilds: true thành mặc định ở v11 và cũng xem xét cải thiện tên gọi của cú pháp allow/deny

Control 2: Release Cooldown

  • Các version mới phát hành gần đây sẽ bị chặn cài đặt trong một khoảng cooldown nhất định, tạo thời gian cho cộng đồng phát hiện và gỡ bỏ package độc hại
  • Ví dụ cấu hình trong pnpm-workspace.yaml gồm các trường sau
    • minimumReleaseAge: <duration-in-minutes>
    • minimumReleaseAgeExclude: danh sách ngoại lệ như hotfix khẩn cấp
  • Cần từ bỏ thói quen “mới nhất là tốt nhất” và chuyển sang tư duy rằng dưới góc độ chuỗi cung ứng, cũ hơn một chút có thể an toàn hơn
  • Trong cuộc tấn công tháng 9 năm 2025 (16 package bao gồm debug, chalk), package độc hại bị gỡ trong khoảng 2,5 giờ; còn Shai-Hulud 2.0 tháng 11 năm 2025 mất khoảng 12 giờ
  • Tùy khả năng chấp nhận rủi ro của tổ chức, thời gian cooldown có thể tính theo giờ/ngày/tuần; dưới bất kỳ hình thức nào thì nó cũng đã ngăn được cuộc tấn công nói trên
  • Cách này cũng phù hợp với thực tế là tổ chức vốn dĩ không phải lúc nào cũng dùng bản mới nhất, nên cooldown không gây cản trở lớn đến công việc
  • Khi thật sự cần như bản vá bảo mật hay lỗi nghiêm trọng, có thể xem xét và mở ngoại lệ

Control 3: Trust Policy

  • Nếu xuất hiện một version được phát hành bằng cơ chế xác thực yếu hơn version trước đó, việc cài đặt sẽ bị chặn
  • Họ xem đây là tín hiệu cho trường hợp tài khoản maintainer bị chiếm đoạt và package được phát hành từ máy của kẻ tấn công thay vì từ CI/CD chính thức
  • Ví dụ cấu hình trong pnpm-workspace.yaml gồm các trường sau
    • trustPolicy: no-downgrade
    • trustPolicyExclude: danh sách ngoại lệ như migration CI/CD
  • npm được mô tả là theo dõi 3 mức độ tin cậy đối với việc phát hành package (mạnh → yếu)
    • Trusted Publisher: dựa trên GitHub Actions + OIDC + npm provenance
    • Provenance: attestation được ký trong CI/CD
    • No Trust Evidence: phát hành bằng username/password hoặc token
  • Nếu version mới có mức độ tin cậy thấp hơn version trước, quá trình cài đặt sẽ thất bại
  • Trong vụ tấn công s1ngularity tháng 8/2025, kẻ tấn công phát hành version độc hại từ máy local mà không có quyền truy cập CI/CD nên không có provenance; trong trường hợp đó cơ chế này đã có thể chặn cài đặt
  • Các trường hợp hạ cấp hợp lệ có thể gồm maintainer mới tham gia, migration CI/CD, hoặc hotfix thủ công khi CI/CD gặp sự cố; sau khi điều tra có thể thêm vào danh sách ngoại lệ
  • Đây là tính năng mới được thêm vào pnpm trong tháng 11 năm 2025, và họ vẫn đang tìm hiểu xem các trường hợp hạ cấp hợp lệ sẽ xuất hiện thường xuyên đến mức nào

Ví dụ cách các lớp phối hợp: vá lỗ hổng React

  • Với kịch bản cần áp dụng ngay bản vá cho lỗ hổng nghiêm trọng trong React Server Components được công bố vào tháng 12 năm 2025
  • Bình thường cooldown sẽ chặn việc “cài một version vừa mới phát hành”, nhưng trong trường hợp bản vá bảo mật nghiêm trọng thì không thể chờ
  • Khi đó có thể thêm version React cụ thể vào minimumReleaseAgeExclude, nhưng chỉ sau khi đã xem xét thông báo lỗ hổng và tính hợp lệ của bản vá
  • Dù áp dụng ngoại lệ, các lớp khác vẫn tiếp tục bảo vệ
    • React thường không có lifecycle scripts, nên nếu bản vá bỗng xuất hiện script thì đó sẽ là tín hiệu đáng ngờ và có thể bị chặn ngay
    • Nếu kẻ tấn công đánh cắp thông tin xác thực rồi phát hành “bản vá” từ máy local, nó có thể bị chặn vì hạ cấp mức độ tin cậy
  • Họ không xem ngoại lệ là “thất bại bảo mật”, mà là một thiết kế trong đó dù đi vòng qua một lớp thì các lớp khác vẫn còn, loại bỏ điểm lỗi đơn lẻ

Kết quả áp dụng thí điểm

  • Họ thực hiện PoC bằng cách áp dụng cả 3 cơ chế kiểm soát cho một backend service
  • Tổng thời gian chuẩn bị, từ điều tra, tìm hiểu đến xác định cách tiếp cận, chỉ mất vài giờ
  • pnpm đã xác định được 3 package có lifecycle scripts
    • esbuild: tối ưu thời gian khởi động CLI xuống mức mili giây, nhưng nhóm chỉ dùng JS API nên đánh giá là không cần thiết
    • @firebase/util: tự động cấu hình client SDK, nhưng nhóm chỉ dùng server SDK nên đánh giá là không cần thiết
    • protobufjs: kiểm tra khả năng tương thích schema; chỉ được dùng như dependency bắc cầu và không cần thiết trong use case của nhóm
  • Sau khi kiểm tra tài liệu và phân tích script (bao gồm dùng AI hỗ trợ diễn giải script), họ kết luận cả ba script đều không cần cho use case của nhóm và chặn chúng
  • Không có tác động chức năng nào xảy ra
  • Ma sát (friction) là một tính năng có chủ đích, vì nó buộc đội ngũ không còn ngầm tin tưởng vào mã sẽ chạy trong môi trường
  • Với dependency mới có script, họ ước tính việc rà soát và tài liệu hóa sẽ mất khoảng 15 phút

Bài học rút ra khi vận hành

  • Sự kết hợp giữa lớp phía client và các cải tiến phía publishing của npm khiến họ cảm nhận rằng defense-in-depth thực sự hoạt động
  • Ngay cả khi áp dụng ngoại lệ, các lớp khác vẫn còn nên cảm giác lo ngại về ngoại lệ cũng giảm đi
  • Việc chuyển đổi mô hình tư duy từ “ưu tiên tiện lợi” sang “ưu tiên bảo mật” cần thời gian, nhưng khi đã quen thì trở nên tự nhiên
  • Ngay cả với tổ chức quy mô trung bình không có đội ngũ bảo mật chuyên trách, cách này vẫn có thể áp dụng thực tế
  • trust policy là tính năng mới chỉ vừa ra mắt vài tuần, nên họ vẫn cần học thêm về tần suất hạ cấp hợp lệ và cảm giác vận hành thực tế
  • Họ dự định mở rộng sang các codebase khác trong tương lai gần, nơi dữ liệu từ các ứng dụng có dependency graph khác nhau sẽ tiếp tục được tích lũy

Mẹo áp dụng cho các đội khác

  • Nên bắt đầu từ một project trước để học workflow và các điểm gây ma sát
  • Vì ngoại lệ có thể cần thiết ở lifecycle scripts, release cooldown và trust downgrade, hãy thiết kế với giả định có ngoại lệ ngay từ đầu
  • Họ khuyến nghị dùng strictDepBuilds: true ngay từ ngày đầu thay vì dựa trên cảnh báo
  • Hãy tài liệu hóa mọi ngoại lệ để tạo audit trail và giúp việc dọn dẹp sau này dễ hơn
  • Hãy nhớ rằng ngoại lệ ở một lớp không xóa bỏ sự bảo vệ của các lớp khác

1 bình luận

 
bichi 2025-12-19

pnpm! pnpm! pnpm! Quả nhiên vẫn đáng để tin tưởng