流水線失敗後,團隊通常會將完整日誌、測試附件與結果套件上傳至共用儲存空間,再交由另一位工程師接手調查。真正危險的往往不是顯眼的 TOKEN=,而是偵錯模式回顯的命令參數、請求標頭、臨時簽章路徑,以及測試程式碼附帶的環境快照。當雲端 Mac 長時間執行多個任務時,這些內容還可能進入快取與封存檔,因此日誌脫敏必須同時在輸出來源、處理管線與上傳門禁三個位置進行。
先畫出日誌的資料流
不要一開始就撰寫一條試圖涵蓋所有文字的正規表示式。第一步應先列出資訊從何處進入、經過哪些程序,以及最終儲存在哪裡。
| 入口 | 常見外洩方式 | 優先處理 |
|---|---|---|
| Shell | set -x 回顯展開後的變數 |
在敏感步驟前關閉追蹤 |
| 命令參數 | 金鑰、權杖直接出現在程序參數中 | 改用權限受限的臨時檔案或標準輸入 |
| 環境變數 | 診斷指令碼輸出完整環境 | 僅輸出允許清單中的項目 |
| 測試 | 請求標頭、帳戶資料寫入附件 | 在測試輔助層統一清理 |
| 結果套件 | 螢幕截圖、活動日誌與診斷檔案被整體上傳 | 匯出後先掃描再發布 |
脫敏無法讓已經外洩的金鑰恢復安全。只要確認真實值曾進入可共用的日誌,就應停止發布該產物,並依金鑰類型執行輪替。
先在 OakVM 節點上為每次任務建立獨立目錄,並將權限設為僅限目前使用者讀寫。日誌、結果套件與待上傳檔案應分開存放,避免掃描後的檔案與原始檔案混在一起。
umask 077
run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$"
root="$HOME/ci-runs/$run_id"
mkdir -p "$root/raw" "$root/safe" "$root/results"
從輸出來源封住外洩入口
不要將機密放進參數
程序參數可能被診斷工具、當機記錄,或同一使用者啟動的指令碼讀取。呼叫內部上傳指令碼時,不要寫成 --token "$TOKEN"。較穩妥的做法是讓指令碼讀取權限設為 600 的臨時檔案,並在使用後立即刪除;若工具支援標準輸入,也可以透過標準輸入傳遞。
Shell 追蹤應預設關閉。確實需要觀察一般命令時,只能在不接觸敏感變數的短暫區段內啟用,並在載入憑證前執行 set +x。同時應禁止直接輸出完整環境,改以允許清單提供診斷資訊:
printf 'PATH=%s
' "$PATH"
printf 'DEVELOPER_DIR=%s
' "$DEVELOPER_DIR"
xcodebuild -version
sw_vers
路徑也應視為敏感資訊。使用者名稱、專案代號與臨時簽章目錄都可能暴露團隊結構。發布日誌前,可以將工作根目錄替換為 $WORKSPACE,但應保留檔名與行號,以免失去除錯價值。
建立不會掩蓋失敗的脫敏管線
替換規則應優先比對真實機密值,而不是只搜尋 password、secret 等欄位名稱。後者不但容易產生誤報,也無法攔截沒有標籤的權杖。以下篩選器會從受限檔案讀取需要遮罩的值,並依長度由長至短進行替換,避免短值破壞長值的比對。
import os
import sys
from pathlib import Path
secret_file = Path(os.environ["REDACT_FILE"])
values = [
line.rstrip("
")
for line in secret_file.read_text(encoding="utf-8").splitlines()
if line.strip()
]
values.sort(key=len, reverse=True)
for line in sys.stdin:
for value in values:
line = line.replace(value, "[REDACTED]")
sys.stdout.write(line)
執行建置時,必須保留 xcodebuild 的真實退出碼。若只檢查管線末端的 tee,失敗的建置可能被誤判為成功。
#!/bin/bash
set -o pipefail
set +x
REDACT_FILE="$root/secrets.txt" \
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-resultBundlePath "$root/results/App.xcresult" \
build 2>&1 |
python3 ci/redact.py |
tee "$root/safe/build.log"
statuses=("${PIPESTATUS[@]}")
exit "${statuses[0]}"
secrets.txt 的權限必須設為 600,並在任務的退出掛鉤中刪除。多行私鑰不適合逐行替換;正確做法是從來源禁止輸出,只將穩定且可能出現在文字中的識別值加入遮罩檔案。
掃描結果套件與測試附件
文字日誌通過檢查,不代表整個任務都可以上傳。結果套件可能包含測試螢幕截圖、失敗附件、活動日誌與診斷資訊。應先將其複製到隔離目錄,匯出確實需要共用的內容,再對匯出目錄執行掃描;不要預設發布完整結果套件。
使用兩類規則降低誤報
第一類規則比對本次任務的真實機密值,一旦命中便立即阻擋。第二類規則檢查高風險結構,例如授權請求標頭、私鑰邊界、包含憑證的 URL,以及疑似長權杖。結構規則命中後應交由人工複核,不要自動將所有 token 字樣判定為外洩,因為原始碼檔名與測試說明也可能包含這個詞。
掃描報告只能記錄檔案路徑、行號與規則編號,不要將命中的原文再次複製到報告中。對於二進位附件,可先識別檔案類型;無法安全解析的檔案預設不得納入共用產物,而不是使用 strings 擷取內容後直接公開。
將脫敏納入發布門禁
一套可維護的門禁至少應包含四種狀態:建置退出碼、精確值掃描、結構規則複核,以及產物允許清單。只要其中任何一項尚未完成,就不應啟動上傳步驟。
建議在每次任務結束時檢查:
- 原始目錄的權限是否仍然僅限目前使用者存取;
- 日誌中是否仍殘留工作根目錄、授權標頭或真實機密值;
- 結果套件是否包含無關的螢幕截圖、網路回應與環境快照;
- 上傳清單是否僅包含已脫敏的日誌、必要報告與明確選取的附件;
- 清理指令碼是否已刪除機密檔案,同時保留仍需調查的安全副本。
最後進行一次反向測試:向臨時任務注入專用的假權杖,讓它分別經過一般輸出、錯誤輸出與測試附件,確認門禁能夠阻擋;接著讓建置主動回傳非零狀態,確認脫敏管線不會將失敗改判為成功。只有這兩類測試同時通過,日誌安全機制才算真正納入工程流程。
常見問題
只在上傳日誌前替換敏感字串是否足夠?
不足。敏感值可能已寫入終端輸出、暫存檔或結果套件;應先關閉除錯追蹤並避免把機密放進命令參數,再把遮罩作為第二道防線。
日誌經過遮罩管線後,如何保留 xcodebuild 的真實退出碼?
在 Bash 啟用 pipefail,並於管線結束後立即保存 PIPESTATUS[0]。不可直接採用 tee 或遮罩程式的退出碼。
除了純文字日誌,還需要檢查哪些建置產物?
至少檢查結果套件、測試附件、診斷壓縮檔、匯出設定副本,以及自訂腳本產生的報告。
在獨享實體節點上執行下一次建置
選擇 OakVM M4 或 OakVM M4 Pro,按日、週、月或季租用非虛擬化的雲端 Mac。節點的實際可用狀態以控制台即時回傳結果為準。