Khắc phục lỗi secrets trống trong pull request fork trên GitHub Actions
GitHub Actions mặc định không cung cấp secrets cho các pull request từ fork. Hãy tìm hiểu các mẫu an toàn như workflow_run để xử lý thông tin đăng nhập mà không làm giảm bảo mật.
Bài được dịch tự động từ bản gốc tiếng Anh.
Khi một pipeline tích hợp liên tục (CI) hoạt động tốt trên các nhánh nội bộ nhưng thất bại ngay lập tức khi nhận đóng góp từ một fork bên ngoài, nguyên nhân gốc rễ thường không phải là lỗi cấu hình. GitHub Actions cố ý chặn quyền truy cập vào repository secrets trong những sự kiện cụ thể này để ngăn chặn việc đánh cắp thông tin đăng nhập. Việc hiểu rõ ranh giới bảo mật này là điều cần thiết để duy trì các pipeline vững chắc trong môi trường mã nguồn mở hoặc cộng tác, đồng thời tránh phơi bày dữ liệu nhạy cảm với code không đáng tin cậy.
Điều gì đã xảy ra
Vấn đề cốt lõi nảy sinh khi một job hoạt động chính xác trên các nhánh tiêu chuẩn lại gặp phải thông tin đăng nhập trống trong quá trình pull request được kích hoạt bởi một repository fork. Hành vi này không phải là bug hay cấu hình sai, mà là một tính năng bảo mật có chủ đích. GitHub Actions giới hạn phạm vi của token mặc định xuống quyền chỉ đọc và hoàn toàn withhold (giữ lại) các secrets do người dùng định nghĩa khỏi các lần chạy này. Hệ thống không cung cấp thiết lập nào để ghi đè lên hạn chế này vì làm vậy sẽ tạo ra một lỗ hổng nghiêm trọng.
Sự thất bại biểu hiện một cách tinh vi. Thay vì hiển thị thông báo lỗi rõ ràng stating rằng secrets bị thiếu, biến secret sẽ được nội suy thành một chuỗi rỗng. Các công cụ downstream sau đó sẽ thất bại với các mã lỗi chung chung của riêng chúng, chẳng hạn như HTTP 401 Unauthorized hoặc phàn nàn về các input bắt buộc bị thiếu. Sự mơ hồ này thường khiến các kỹ sư lãng phí thời gian debug kết nối mạng hoặc phiên bản công cụ thay vì nhận ra mô hình phân quyền đang diễn ra. Một bước chẩn đoán đơn giản có thể xác nhận vấn đề bằng cách kiểm tra xem nguồn sự kiện có phải là fork hay không và giá trị secret có non-empty hay không, mang lại sự rõ ràng tức thì.
Thiết kế này tồn tại bởi vì code thực thi trong một pull request fork được kiểm soát bởi contributor, chứ không phải owner của repository. Nếu secrets khả dụng, một kẻ tấn công độc hại có thể sửa đổi script build hoặc file test để exfiltrate (đánh cắp) deploy keys và API tokens chỉ trong một commit. Bằng cách vẽ ranh giới tin cậy tại điểm thực thi code, GitHub đảm bảo rằng code không đáng tin cậy không bao giờ có quyền truy cập vào các biến môi trường nhạy cảm. Các trigger duy nhất mang theo đầy đủ secrets là những trigger chạy trong ngữ cảnh repository cơ sở, chẳng hạn như các target events cụ thể hoặc các workflow runs tiếp theo.
Chi tiết quan trọng
- GitHub Actions withhold tất cả repository secrets khỏi các
pull_requestevents bắt nguồn từ forks. GITHUB_TOKENtự động được giới hạn phạm vi xuống quyền chỉ đọc cho các events này.- Các thất bại xuất hiện dưới dạng lỗi xác thực chung chung hoặc cảnh báo thiếu input thay vì các vấn đề secret rõ ràng.
- Trigger
pull_request_targetcung cấp secrets nhưng mặc định checkout base code, đòi hỏi phải xử lý cẩn thận để tránh thực thi code PR không đáng tin cậy. - Các pull request của Dependabot cũng không có quyền truy cập vào standard repository secrets, yêu cầu cấu hình riêng biệt trong kho lưu trữ secrets của Dependabot.
- Sử dụng
workflow_runcho phép một workflow thứ hai, đáng tin cậy, truy cập secrets sau khi một build ban đầu không đáng tin cậy hoàn tất.
Bối cảnh
Để hiểu tại sao hạn chế này tồn tại, ta cần nhận biết sự khác biệt giữa các ngữ cảnh code đáng tin cậy và không đáng tin cậy. Trong một branch push tiêu chuẩn, code thuộc về các collaborator của repository, những người đã có quyền truy cập vào tài nguyên dự án. Trong một pull request fork, code đến từ một người dùng bên ngoài có thể không có quyền write. Nếu hệ thống CI thực thi code bên ngoài này với đặc quyền quản trị đầy đủ, nó sẽ thực tế trao cho mọi contributor khả năng đánh cắp thông tin đăng nhập.
Giải pháp liên quan đến việc tách biệt quy trình build khỏi các giai đoạn triển khai hoặc kiểm thử tích hợp. Giai đoạn đầu tiên chạy code không đáng tin cậy trong một môi trường sandboxed mà không có secrets. Khi giai đoạn này tạo ra các artifacts, chẳng hạn như binary đã biên dịch hoặc báo cáo test, một giai đoạn thứ hai có thể tiêu thụ các artifacts này trong một ngữ cảnh đáng tin cậy. Sự tách biệt này đảm bảo rằng trong khi code được kiểm thử, nó không bao giờ tương tác trực tiếp với thông tin đăng nhập hạ tầng nhạy cảm trừ khi được phê duyệt rõ ràng và cô lập.
Tại sao điều này quan trọng
Đối với các đội ngũ quản lý dự án mã nguồn mở hoặc chấp nhận đóng góp từ bên ngoài, việc bỏ qua mô hình bảo mật này có thể dẫn đến các vi phạm nghiêm trọng. Cố gắng vượt qua hạn chế bằng cách sử dụng các trigger không an toàn như pull_request_target kèm theo checkout head của pull request sẽ tạo ra kịch bản "pwn request". Trong tình huống này, CI runner thực thi code tùy ý từ contributor với quyền truy cập đầy đủ vào tất cả repository secrets. Rủi ro này đặc biệt cao trong các public repositories phổ biến, nơi các hacker chủ động quét tìm những cấu hình sai như vậy để thu thập cloud credentials.
Ngược lại, việc không xử lý đúng cách dẫn đến trải nghiệm kém cho contributor. Khi các developer bên ngoài gửi pull request thất bại do thiếu thông tin đăng nhập, họ có thể cho rằng dự án được bảo trì kém hoặc bị hỏng. Ma sát này làm nản lòng sự tham gia của cộng đồng và tăng gánh nặng bảo trì cho các thành viên core team, những người phải giải thích thủ công về sự thất bại. Việc triển khai logic skip rõ ràng hoặc các workflow đáng tin cậy riêng biệt đảm bảo rằng contributors nhận được phản hồi chính xác trong khi vẫn giữ cho môi trường production an toàn.
Ngoài ra, nhiều đội ngũ bỏ qua thực tế rằng một số bài kiểm tra tích hợp không thực sự yêu cầu live secrets. Bằng cách thay thế các cuộc gọi dịch vụ bên ngoài bằng local stubs hoặc service containers, các đội ngũ có thể chạy các bài kiểm tra toàn diện hơn trong ngữ cảnh fork không đáng tin cậy. Cách tiếp cận này giảm nhu cầu về các thiết lập multi-workflow phức tạp và đẩy nhanh vòng lặp phản hồi cho contributors. Nó chuyển trọng tâm từ việc quản lý quyền truy cập secrets sang cải thiện sự cô lập và độ tin cậy của test.
Bạn có thể làm gì
- Audit các file workflow của bạn để xác định những steps thực sự yêu cầu secrets so với những steps có thể sử dụng local stubs hoặc service containers.
- Triển khai một conditional check trong các jobs của bạn để bỏ qua các steps phụ thuộc vào secrets khi chạy trên pull request fork, sử dụng một thông báo hiển thị để giải thích việc bỏ qua.
- Sử dụng event
workflow_runđể kích hoạt một workflow thứ hai tải xuống artifacts từ build ban đầu và thực hiện các tasks phụ thuộc vào secrets trong một ngữ cảnh đáng tin cậy. - Nếu sử dụng
pull_request_target, hãy đảm bảo bạn không bao giờ checkout SHA head của pull request; luôn tuân thủ code của base branch để ngăn chặn injection code. - Cấu hình các secrets riêng biệt cho Dependabot nếu các bản cập nhật dependency tự động của bạn thất bại, vì chúng không chia sẻ kho lưu trữ secrets chính của repository.
- Post các commit statuses thủ công từ các workflow đáng tin cậy để đảm bảo contributors nhìn thấy kết quả của các checks phụ thuộc vào secrets trên pull request của họ.



