工程文章

雲端 Mac CI 下載工具遭 Gatekeeper 阻擋:診斷隔離屬性並安全放行

雲端 Mac CI 下載工具遭 Gatekeeper 阻擋:診斷隔離屬性並安全放行

建置任務在互動式終端機中執行正常,換到無人值守的 runner 後卻出現「無法開啟」、「不允許此操作」,或是程序才剛啟動就結束。許多人第一時間會執行 chmod +x,但如果檔案本來就有執行權限,真正的阻擋來源通常是 Gatekeeper、程式碼簽章評估,或下載檔案所帶的隔離屬性。常見的觸發情境包括從瀏覽器下載工具、複製其他工作階段的產物,以及從會保留延伸屬性的封存檔還原快取。

先區分四類執行失敗

看到「無法執行」時,不要立刻刪除屬性。先收集檔案類型、權限、架構與延伸屬性;這四項結果足以排除大多數誤判。

TOOL="/opt/build-tools/example-tool"

ls -leO@ "$TOOL"
file "$TOOL"
uname -m
xattr -l "$TOOL"

ls 用來確認擁有者與執行位元;file 應顯示與節點相符的可執行檔架構;如果 xattr 顯示 com.apple.quarantine,只能代表檔案曾進入隔離評估流程,不能直接證明檔案安全。若錯誤為 Permission denied,請先檢查目錄是否允許逐層存取,以及掛載點是否設有執行限制。若錯誤為 Bad CPU type in executable,應改用架構正確的產物,而不是處理隔離屬性。

現象 優先檢查 不應立即執行的操作
Permission denied 檔案與上層目錄權限 遞迴清除延伸屬性
Bad CPU type fileuname -m 反覆修改執行位元
已損毀或無法驗證 簽章與 Gatekeeper 結果 關閉系統安全檢查
僅 runner 失敗 實際路徑、使用者與快取來源 假設互動式環境完全相同

隔離屬性是來源線索,不是故障本身。先證明取得的是預期檔案,再決定是否放行。

讀取 Gatekeeper 的明確結論

評估單一二進位檔或應用程式

對命令列工具,先檢查簽章,再要求系統原則提供評估結果。若要檢查應用程式套件,可將 TOOL 改為對應的 .app 路徑。

codesign --display --verbose=4 "$TOOL"
codesign --verify --deep --strict --verbose=2 "$TOOL"
spctl --assess --type execute --verbose=4 "$TOOL"

未簽章的內部工具不一定有問題,但必須來自受控的建置流程,並透過摘要驗證證明其完整性。如果第三方預先編譯工具宣稱帶有簽章,驗證失敗時就應停止使用並重新取得可信產物,不能靠刪除隔離屬性掩蓋簽章變更。

查詢原則日誌

彈出式視窗與 runner 的標準錯誤通常會省略原因。重現問題後,立即讀取近期的安全原則記錄,即可確認實際接受評估的路徑,避免檢查的是快取 A,真正執行的卻是快取 B。

log show --last 10m \
  --predicate 'subsystem == "com.apple.security.syspolicy"' \
  --style compact

將日誌中的路徑逐一與 command -v、runner 設定及指令碼變數核對。符號連結尤其容易造成偏差:入口位於工具目錄,但最終檔案可能來自舊快取。

放行前先驗證產物身分

最穩妥的流程,是由工具發布方或內部產物流程提供 SHA-256 摘要,並將摘要檔與工具版本一起納入儲存庫設定。不要針對下載結果臨時計算摘要,再用它驗證同一份檔案;那只能證明檔案在兩次讀取之間沒有變化,無法證明來源正確。

cd /opt/build-tools
shasum -a 256 -c example-tool.sha256
codesign --verify --deep --strict --verbose=2 example-tool

內部自行編譯的工具可以沒有對外散布用的簽章,但至少應保留提交版本、建置指令碼版本與預期摘要。如果工具以壓縮檔提供,應先驗證壓縮檔,再解壓縮至暫存目錄,檢查最終可執行檔,最後以不可分割操作取代正式路徑。如此一來,runner 就不會在更新途中讀到不完整的檔案。

只清除目標檔案的隔離屬性

確認摘要、版本與簽章都符合預期後,只處理已驗證的目標。先讀取原始值並寫入作業日誌,再刪除指定屬性。

TOOL="/opt/build-tools/example-tool"

xattr -p com.apple.quarantine "$TOOL" 2>/dev/null || true
if xattr -p com.apple.quarantine "$TOOL" >/dev/null 2>&1; then
  xattr -d com.apple.quarantine "$TOOL"
fi

spctl --assess --type execute --verbose=4 "$TOOL"
"$TOOL" --version

不要對工作區執行 xattr -cr .。這會同時清除儲存庫、相依套件、指令碼與暫存產物的多種延伸屬性,不但擴大放行範圍,也會讓後續調查失去來源證據。應用程式套件確實可能需要處理套件內繼承的屬性,但範圍仍應限定在已驗證的單一套件,而不是 runner 的整個快取根目錄。

將檢查固化為安裝門禁

穩定的雲端 Mac CI 不應在每個建置任務中臨時修正權限。應將工具準備流程拆成獨立的安裝階段:下載至暫存目錄、核對摘要、檢查架構、驗證簽章、讀取隔離狀態、定點放行、執行版本自我檢查,最後才寫入共用工具目錄。

快取鍵至少應包含工具名稱、版本、CPU 架構與摘要。即使命中快取,仍須執行輕量檢查,因為快取可能遭到手動覆寫。runner 啟動時可以記錄 id -ununame -m、工具解析後的路徑與版本,但不要輸出認證資訊或完整的環境變數。

最後請保留三項失敗處理原則:摘要不符時立即停止;原本應有簽章卻驗證失敗時重新取得產物;只有隔離評估阻擋了已驗證的產物時,才刪除目標屬性。這種做法雖然比全域關閉檢查多了幾道命令,卻能讓工具來源、放行操作與實際執行版本都可供稽核。

常見問題

為什麼工具已有執行權限,仍會遭 macOS 阻擋?

執行權限只控制檔案能否被啟動。Gatekeeper 還會檢查隔離屬性、程式碼簽章、來源與系統安全政策,因此 chmod +x 無法處理所有阻擋情況。

可以對整個 CI 工作目錄遞迴執行 xattr -cr 嗎?

不建議。這會移除目錄內所有檔案的擴充屬性並掩蓋未知產物的來源。應先驗證雜湊與簽章,再只刪除目標檔案的 com.apple.quarantine。

如何避免同一工具在每次建置時重複遭阻擋?

把下載、雜湊驗證、解壓縮與定點放行放入受控的工具安裝階段,並以工具版本、架構及雜湊作為已驗收檔案的快取鍵。

OrbVPS 雲端 Mac

在獨享 Apple Silicon 實體節點上執行建置

依機型、區域與租用週期設定節點,實際可用狀態以控制台即時回傳結果為準。

立即租用雲端 Mac