- Tấn công chuỗi cung ứng là cách các bản cập nhật độc hại len lỏi vào mã nguồn mở, và để giảm thiểu điều này, Obsidian áp dụng chiến lược giảm chính các phụ thuộc bên ngoài
- Phần lớn chức năng của ứng dụng được tự triển khai trực tiếp, hoặc khi cần thì sử dụng mã đã được fork và đưa vào codebase nội bộ để quản lý
- Với các thư viện lớn bắt buộc như pdf.js, Mermaid, MathJax, Obsidian đảm bảo an toàn bằng cách ghim phiên bản, và chỉ cập nhật một cách thận trọng khi có bản vá bảo mật
- Mọi phụ thuộc đều được cố định bằng lockfile, đồng thời không chạy script postinstall để chặn việc thực thi mã tùy ý trong quá trình cài đặt
- Thông qua quy trình cập nhật thận trọng và chiến lược trì hoãn theo thời gian này, Obsidian có thể ứng phó trước khi các mối đe dọa tiềm tàng bị cộng đồng phát hiện rộng rãi
Tấn công chuỗi cung ứng là gì
- Tấn công chuỗi cung ứng là cách các bản cập nhật độc hại ẩn mình trong mã được phân phối trong hệ sinh thái mã nguồn mở
- Vì nhiều ứng dụng sử dụng mã nguồn mở, một bản cập nhật độc hại duy nhất có thể lan rộng và ảnh hưởng đến nhiều ứng dụng
- Obsidian giảm bề mặt tấn công này và thiết kế ứng dụng an toàn hơn bằng chiến lược tối thiểu hóa phụ thuộc
Chiến lược tối thiểu hóa phụ thuộc: Less is Safer
- So với các ứng dụng khác trong cùng danh mục, Obsidian phụ thuộc vào rất ít thư viện bên ngoài
- Các tính năng chính (ví dụ: Bases, Canvas) được tự triển khai thay vì đưa vào thư viện bên ngoài
- Nhờ đó có thể nắm quyền kiểm soát hoàn toàn đối với mã đang được thực thi
- Các hàm tiện ích nhỏ hầu như luôn được đội ngũ phát triển tự viết
- Các mô-đun quy mô trung bình sẽ được fork và đưa vào codebase nếu giấy phép cho phép
- Các thư viện lớn (pdf.js, Mermaid, MathJax, v.v.) được ghim ở các phiên bản đã được kiểm chứng và chỉ nâng cấp ở mức tối thiểu khi phát hiện vấn đề bảo mật quan trọng
- Mọi thay đổi từ bên ngoài đều được xem xét chi tiết và trải qua quy trình kiểm thử nghiêm ngặt
- Theo cách này, Obsidian cũng giảm thiểu số lượng phụ thuộc con, từ đó hạ thấp rủi ro ban đầu để mã độc xâm nhập
Những thành phần thực sự được đưa vào ứng dụng
- Ứng dụng mà người dùng thực sự chạy chỉ bao gồm một số rất ít package như Electron, CodeMirror, moment.js
- Các công cụ phát triển khác chỉ được dùng trong quá trình build ứng dụng và không được chuyển đến người dùng cuối
Ghim phiên bản và quản lý lockfile
- Mọi phụ thuộc bên ngoài đều được quản lý thông qua ghim phiên bản nghiêm ngặt (pin) và commit lockfile
- Nhờ đó, việc cài đặt luôn có thể tái lập và dễ dàng theo dõi lịch sử thay đổi
- Chính sách không chạy script postinstall chặn tận gốc khả năng thực thi mã tùy ý trong lúc cài đặt
Quy trình cập nhật chậm rãi và thận trọng
- Khi cần nâng cấp phụ thuộc, Obsidian thực hiện quy trình xem xét có hệ thống như sau
- Rà soát tỉ mỉ từng dòng trong changelog
- Kiểm tra các phụ thuộc cấp dưới mới được thêm trong phiên bản mới
- Nếu mức thay đổi lớn hoặc có yếu tố rủi ro, trực tiếp kiểm tra diff với mã upstream
- Thực hiện kiểm thử tự động/thủ công trên các luồng chính và các nền tảng quan trọng
- Chỉ commit lockfile sau khi vượt qua toàn bộ các bước trên
- Vì phần lớn phụ thuộc không cần thay đổi thường xuyên, nên bản thân tần suất cập nhật cũng thấp
- Việc đưa vào mã bên ngoài mới được xem xét và quản lý ở mức tương đương với việc chấp nhận một phụ thuộc mới
"Khoảng đệm thời gian" cho độ ổn định: Time is a buffer
- Các đợt nâng cấp không được phát hành ngay lập tức mà sẽ bị trì hoãn trong một khoảng thời gian nhất định
- Trong thời gian này, cộng đồng mã nguồn mở và các nhà nghiên cứu bảo mật có thể phát hiện các phiên bản độc hại, đóng vai trò như một cửa sổ phản ứng sớm
- Đến thời điểm phát hành thực tế, khả năng vấn đề đã được xác nhận là cao hơn, nên rất hiệu quả trong việc giảm thiểu rủi ro
Kết luận
- Không có một biện pháp bảo mật đơn lẻ nào có thể loại bỏ hoàn toàn rủi ro tấn công chuỗi cung ứng
- Tuy nhiên, Obsidian đang giảm mạnh rủi ro bằng cách kết hợp tối thiểu hóa phụ thuộc, đồ thị phụ thuộc nông, ghim phiên bản, cấm postinstall, và cập nhật chậm dựa trên rà soát kỹ lưỡng
- Những quy trình này cũng giúp tạo thêm đáng kể thời gian để phát hiện rủi ro trước khi mã đến tay người dùng
- Có thể xem toàn bộ cách tiếp cận bảo mật của Obsidian và lịch sử các đợt audit bảo mật trước đây tại security page chính thức
1 bình luận
Ý kiến trên Hacker News
Nhiều ý kiến dường như bỏ qua việc phần lớn người dùng Obsidian có dùng plugin cộng đồng bên thứ ba; trên thực tế, mô hình bảo mật plugin của Obsidian rất yếu, vì plugin được cấp quyền truy cập toàn bộ tệp trong vault. Nếu Obsidian tự tích hợp thêm nhiều tính năng hơn thì rủi ro bảo mật đã có thể giảm đi; hoặc cũng có thể cải thiện theo hướng giống extension trình duyệt: nêu rõ các quyền plugin sử dụng và chặn việc truy cập các quyền chưa được cho phép. Tất cả những điều này mang lại bảo mật thực tế cho người dùng hơn nhiều so với lập luận “ít phụ thuộc bên thứ ba hơn”.
Trước đây từng nghe một số người trong ngành phần mềm nói về những ý tưởng từ thiết kế game chảy sang phần mềm nói chung, nhưng giờ gần như không còn nghe nữa. Nếu những nhân sự cốt lõi ở các công ty game lâu đời như Blizzard viết một cuốn sách tổng kết chi tiết cách hệ thống plugin của World of Warcraft vận hành trong 10 năm đầu, những vấn đề đã gặp và quá trình tăng cường bảo mật, thì sẽ rất có ích cho toàn bộ hệ sinh thái phần mềm. Hệ thống plugin của nhiều dự án hiện nay là sự pha trộn giữa lỏng lẻo và vụng về.
Plugin Obsidian không chỉ truy cập được tệp trong vault mà còn có thể truy cập mọi tệp trên máy tính. Tôi từng chỉ ra điểm này trên Discord trước đây nhưng đã bị phớt lờ.
Tôi nghĩ nó tương tự Arch Linux; ngay cả kỹ sư cũng thấy bảo mật khó khi tự quản lý phần mềm từ AUR. Kỳ vọng người dùng phổ thông phải tự chịu trách nhiệm về bảo mật là điều quá sức.
Tôi nghĩ sớm muộn cũng sẽ lộ ra một vụ rò rỉ dữ liệu từ plugin Obsidian; đến lúc đó đội ngũ mới sẽ đưa các biện pháp bảo vệ vào. Ít nhất cũng cần có hệ thống publisher đã được xác thực.
Tôi đang phát triển plugin Obsidian theo hướng thương mại. Tôi mong có quy trình xét duyệt cấp cao hơn cho những plugin ở mức độ nhất định. Nếu vận hành hai kho như AUR của Arch Linux — một kho do cộng đồng quản lý và một kho xét duyệt nghiêm ngặt hơn — thì sẽ giúp vừa cải thiện tốc độ review plugin vừa nâng cao bảo mật.
Có đoạn mô tả rằng “tấn công chuỗi cung ứng là khi một bản cập nhật độc hại ẩn vào mã nguồn mở được nhiều ứng dụng dùng”, nhưng bất kỳ mã nguồn nào, không chỉ FOSS, đều có thể là mục tiêu tấn công. Cách nhìn cho rằng FOSS nhất định dễ tổn thương hơn là có vấn đề.
Chính sách “không chạy script postinstall trong lúc cài đặt” là có ý tốt, nhưng nếu package đã bị xâm phạm thì việc bỏ qua postinstall cũng không làm phần mã còn lại trở nên an toàn. Nếu package lành tính thì postinstall cũng có thể hỗ trợ việc cài đặt hợp lệ. Trên thực tế, sự cố thường xuất phát từ các bản vá lỗ hổng thông thường nhiều hơn là từ tấn công chuỗi cung ứng, nên chặn cập nhật có thể còn làm tăng rủi ro.
Ngày nay việc quét bảo mật thường diễn ra sau khi cài đặt (post install). Trong lúc cài thì tốt nhất là ngăn không cho bất cứ thứ gì chạy. Tôi hy vọng sau này sẽ có thêm nhiều tính năng quét hoặc hạn chế ngay ở giai đoạn cài đặt; một số công cụ thương mại có hỗ trợ nhưng chưa phổ biến.
Dù vậy, điều đó vẫn có ý nghĩa trong việc bảo vệ máy build; tôi không phải lo script tùy ý bị chạy từ vô số dependency của mình.
Trách nhiệm với mọi đoạn mã phân phối cho người dùng thuộc về nhà phát triển. Nếu không pin dependency thì khác nào “tải mã ngẫu nhiên từ Internet rồi cầu may”.
Pin dependency có thể khiến bạn bỏ lỡ các bản vá bảo mật về sau. Điều này cũng nguy hiểm, nên nhất định phải có hệ thống giúp nhận biết khi có bản vá bảo mật mới. Khi đó cần backport bản vá hoặc cập nhật lên phiên bản mới.
Ngay cả dependency đã pin cũng lại chứa các dependency khác, nên rốt cuộc vẫn luôn là cấu trúc “tải mã ngẫu nhiên rồi hy vọng điều tốt đẹp”. Đặc biệt với ứng dụng nền Electron thì lượng mã đi kèm thực sự khổng lồ.
Gần đây tôi hay thấy lời khuyên rằng ngay cả khi có patch release cũng đừng cập nhật dependency, mà thật sự không hiểu nổi. Không cập nhật có thể làm giảm nguy cơ cài phải mã độc, nhưng thông thường patch là để cải thiện bảo mật. Tôi tự hỏi liệu không áp dụng bản vá mới nhất có thực sự là lựa chọn khôn ngoan hay không.
Điều cốt lõi là hiểu vì sao áp dụng bản vá và nội dung thay đổi là gì. Không ai có thời gian đọc hết mọi mã nguồn, nên tôi dùng các công cụ và dịch vụ chính như Npm Audit để xem thông tin tóm tắt về lỗ hổng. Chiến lược của tôi là trì hoãn cập nhật trừ khi thật sự cần, vì cập nhật vừa là vector tấn công vừa là nguyên nhân lớn gây bug. Tuy vậy, tôi luôn định kỳ kiểm tra xem mình đang phơi nhiễm với lỗ hổng nào; nếu là lỗ hổng ở tính năng không dùng tới thì có thể trì hoãn cập nhật, còn lỗi thực sự nghiêm trọng thì cập nhật ngay. Bảo mật là một quá trình chủ động và liên tục, và cách ứng phó phải thay đổi theo mức độ chấp nhận rủi ro của tổ chức; không thể chỉ có đáp án “luôn cập nhật” hoặc “không bao giờ cập nhật”.
Dạo này mỗi lần cập nhật Z-WaveJS UI là dependency lại bị cập nhật lặp đi lặp lại. Vì kiểu mẫu gây khó chịu này nên tôi tự kiểm tra trực tiếp. Ngày nay ai cũng phụ thuộc vào dependency của dependency, nên “không có hồi kết”; chỉ một lần auto-update thôi cũng đã nguy hiểm.
Khi cập nhật luôn tồn tại rủi ro cải thiện hoặc thoái lui, và trong hệ sinh thái npm (Obsidian thuộc nhóm này) thì rủi ro đó lớn hơn nhiều. npm cần con người can thiệp để gỡ package độc hại nên xử lý khá chậm. Cố tình trì hoãn cập nhật phần nào còn giúp phòng thủ được.
Gần đây có xu hướng chờ một chút sau khi patch release vừa ra rồi mới cài. Bây giờ sự cố thường bị phát hiện trong vòng vài giờ; nhiều công ty đang giám sát npm và cũng làm kinh doanh bảo mật từ đó. pnpm có thể cấu hình để chỉ cài package đã được phát hành quá X phút; tôi thường chờ ít nhất khoảng 24 giờ thiết lập minimumreleaseage của pnpm
Cuộc tấn công mà package của tôi hứng chịu cách đây 2 tuần nhắm đúng vào patch release. Nó không phải là script postinstall. Việc quét tự động phát hiện rất nhanh nên vấn đề này giờ không còn quá nổi bật; khi phát hiện lỗ hổng trong package thì cảnh báo đến ngay và rất rõ ràng. Dùng version ranges là cách tệ nhất để ứng phó với tấn công chuỗi cung ứng.
Tôi khó đồng ý với nhận định rằng gói không lớn vì chỉ gồm Electron, CodeMirror và moment.js. Electron là phần mềm có độ phức tạp cực lớn vì dựa trên webview, còn moment.js thì đã có API tốt hơn từ lâu. Mức độ quản lý dependency của Obsidian theo tôi chỉ mới đạt ngưỡng tối thiểu chứ chưa thể xem là chính sách bảo mật đặc biệt xuất sắc; dù vậy, việc họ kiểm toán bảo mật định kỳ vẫn là điểm tích cực.
Tôi từng dùng các ứng dụng khác thay vì Obsidian, nhưng giờ cũng bắt đầu thấy hứng thú với Obsidian. Việc nó là ứng dụng Electron khiến nó tốn tài nguyên và không phải native, nên JavaScript lúc nào cũng tạo cho tôi cảm giác bất an. Không biết có phải tôi quá hoài cổ hay không.
JavaScript là một ngôn ngữ rất an toàn. Trình duyệt web là ví dụ thành công trên toàn cầu về việc chạy JavaScript một cách an toàn; mỗi website không thể đọc dữ liệu của website khác. Electron cũng sandbox JavaScript trong engine v8. Chỉ cần tránh thực thi mã do người dùng nhập vào thì nó khá an toàn. Vấn đề tấn công chuỗi cung ứng nằm ở npm chứ không phải ở bản thân ngôn ngữ JS; npm phải chịu trách nhiệm áp dụng chính sách bảo mật nghiêm ngặt hơn khi phát hành package.
JavaScript, xét theo hầu như mọi tiêu chí, là một trong những ngôn ngữ được dùng nhiều nhất thế giới. Nó chạy trên gần như mọi máy tính và smartphone, vì vậy các vấn đề bảo mật được phát hiện thường xuyên hơn. Không có cơ sở nào để nói rằng ứng dụng native thì an toàn hơn.
Ứng dụng Electron không phải là vấn đề. GitHub, VS Code, Slack, Discord, Postman đều dựa trên Electron. Tôi hay muốn hỏi rằng trong một ứng dụng ghi chú Markdown thì người ta thật sự làm công việc gì mà hiệu năng lại có thể thành vấn đề? Đôi khi còn khiến tôi tự hỏi có phải họ đang dùng laptop cổ đến mức xem bằng trình duyệt Lynx ở chế độ chỉ văn bản không.
Tôi không quá thích Electron (nên mới thích tauri), nhưng bản thân Obsidian vẫn rất xuất sắc, không cần vì Electron mà quay lưng với nó. Nó cũng rất hợp để tích hợp với MCP và dùng như một kho tri thức cá nhân, nên tôi khuyên dùng.
Electron đúng là nặng. Trên PC thì không thành vấn đề lớn, nhưng trên di động, khi số ghi chú lên đến hàng nghìn, ứng dụng khởi động chậm ngay cả khi không dùng plugin. Người dùng đang lách qua bằng plugin/ứng dụng để ghi chép nhanh, nhưng tôi vẫn mong có một Obsidian native nhẹ hơn.
Obsidian dựa trên Electron, và theo đặc tính đó thì đi kèm sự nặng nề và nguy cơ lỗ hổng bảo mật.
Tôi dùng Emacs và Org-Roam, chạy trong một VM không có kết nối mạng (Qube của Qubes OS). Tôi không thể tự mình review mọi đoạn mã chạy trong Emacs.
Nếu muốn ứng dụng native và muốn giảm thêm rủi ro chuỗi cung ứng, Zim Wiki là một lựa chọn thay thế: nó dựa trên GTK và được đóng gói trong các bản phân phối Linux chính Xem Zim Wiki