После сбоя конвейера команда обычно загружает полные журналы, тестовые вложения и пакеты результатов в общее хранилище, чтобы другой инженер мог продолжить расследование. Основную опасность представляет не заметная строка 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 после фильтрации?
Включите pipefail в Bash и сразу после конвейера сохраните PIPESTATUS[0]. Код завершения tee или фильтра не должен подменять результат сборки.
Что проверять помимо обычного текстового журнала?
Проверяйте пакеты результатов, вложения тестов, диагностические архивы, копии экспортных настроек и отчёты пользовательских сценариев.
Запустите следующую сборку на выделенном физическом узле
Выберите OakVM M4 или OakVM M4 Pro и арендуйте облачный Mac без виртуализации на день, неделю, месяц или квартал. Фактическая доступность узла отображается в консоли в режиме реального времени.