流水线失败后,团队通常会把完整日志、测试附件和结果包上传到共享存储,再由另一位工程师接手排查。真正危险的不是那句显眼的 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。节点实际可用状态以控制台实时返回为准。