Гайд · TNWS AI

Как безопасно тестировать AI-код из форка без API-ключей в GitHub Actions

6 мин—

Безопасный workflow для pull request из форка: минимальные permissions, локальные фикстуры, отсутствие секретов и проверка результата.

Задача и границы применения

Принимать внешние PR в репозиторий AI-приложения, не предоставляя недоверенному коду ключ модели или запись в репозиторий. Для публичного проекта вкладчики могут изменять код и тесты в своих форках. Любой job, который делает checkout PR и запускает тест, исполняет недоверенный код. Поэтому workflow ограничивает GITHUB_TOKEN до contents: read и не использует secrets. Отдельный доверенный smoke-тест с секретом запускается только в контролируемом контексте. Ниже — сценарий для собственного репозитория, а не обещание универсальной точности генеративной модели. Для демонстрации используются вымышленные обращения, которые не содержат персональных данных.

Актуальность названий полей, событий и версий actions проверена 19.09.2026 по официальным материалам GitHub. Если у вашего проекта другая структура каталогов или другой протокол AI API, адаптируйте пути и прикладной клиент после проверки его документации. Пример не содержит утверждений о тарифах, ценах или скорости.

Что подготовить до изменения workflow

Нужен репозиторий GitHub, доступ к его Actions и возможность добавить файл в каталог .github/workflows. Подготовьте тестовую ветку и одно синтетическое обращение с известным ожидаемым результатом. Не берите production-заявки: даже если runner временный, ввод и диагностика оказываются в истории запуска. Для Python-части используйте unittest из стандартной библиотеки; если в вашем проекте есть дополнительные импорты, установите их отдельно из контролируемого файла зависимостей.

До коммита запишите контракт: какие поля обязательно должны быть в результате, какие значения разрешены и при каком несоответствии job получает ненулевой код выхода. Сначала выполните тот же скрипт локально. Это позволяет различить ошибку Python от ошибки описания workflow. Если проверка обращается к внешней модели, разделите транспортную ошибку, отсутствие ключа и несоответствие ответа: эти причины требуют разного исправления.

Пошаговая настройка

  1. Создайте нужные каталоги и файлы из блоков ниже; имена путей в YAML должны совпадать с именами файлов в репозитории. Файл workflow хранится внутри .github/workflows, а тестовый код — в tests или scripts согласно примеру.
  2. Проверьте три секции YAML: on задаёт событие, permissions ограничивает встроенный токен, jobs содержит runner и последовательность шагов. Не вставляйте ключи или закрытые тексты в YAML.
  3. Сохраните workflow и локальную фикстуру ниже. Держите permissions: contents: read на уровне workflow; это ограничивает встроенный GITHUB_TOKEN, а не права администратора проекта. Не добавляйте env: AI_API_KEY: secrets... и не используйте pull_request_target для checkout и исполнения кода из PR: этот контекст требует отдельного анализа доверия. Создайте тестовый PR из форка и убедитесь, что проверка отрабатывает без ключа. Затем измените ожидаемый результат в тесте — job должен стать красным. Для проверки реального API используйте отдельный workflow на доверенной ветке с ограниченным секретом и синтетическим запросом.
  4. Сначала воспроизведите успешный вход, затем намеренно измените одно ожидаемое значение и убедитесь, что job падает. Проверка без негативного сценария не доказывает, что тест вообще способен обнаружить регрессию. После этого верните правильное значение и повторите запуск.
  5. При сбое откройте конкретный job и шаг, а не ориентируйтесь только на общий статус workflow. Проверьте событие запуска, выбранную ветку, наличие файлов, код завершения скрипта и последние безопасные диагностические сообщения.

Готовый шаблон workflow

Скопируйте YAML; если в блоке указаны два имени файла, разделите его на два соответствующих файла. Ссылки на внешние actions и параметры проверены по их официальным README на 19.09.2026.

name: Untrusted PR contract
on:
  pull_request:
permissions:
  contents: read
jobs:
  offline-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: '3.13'
      - name: Test deterministic contract
        run: python -m unittest discover -s tests -v

Код или тестовая фикстура для копирования

Этот блок даёт исходную контрольную точку. Встраивая его в приложение, заменяйте демонстрационный нормализатор на собственный импорт, сохраняя контракт и синтетические тестовые входы.

# tests/test_prompt_contract.py
import unittest

def render_ticket(text):
    # Замените на детерминированную часть своего приложения.
    # Сетевой вызов модели здесь намеренно отсутствует.
    return {"priority": "high" if "срочно" in text.lower() else "normal",
            "summary": text.strip()}

class PromptContract(unittest.TestCase):
    def test_urgent_ticket(self):
        actual = render_ticket("Срочно: платеж не прошёл")
        self.assertEqual(actual["priority"], "high")
        self.assertEqual(actual["summary"], "Срочно: платеж не прошёл")

if __name__ == "__main__":
    unittest.main()

Реалистичный вход и ожидаемый результат

Внешний PR с корректной обработкой Срочно: платеж не прошёл получает зелёный check, при возврате normal — красный. Ни успешный, ни неуспешный job не должен выводить или получать AI_API_KEY. Перед тем как трактовать красный job как баг модели, убедитесь, что сравниваются значения одной и той же ревизии кода и одной и той же фикстуры. Не подменяйте ожидаемое значение фактически полученным: такое «исправление» отключит сигнал регрессии.

Разбор ошибок и ограничения

Если workflow не появляется в Actions, сперва проверьте расположение YAML и событие on; затем проверьте ограничения по путям и веткам. Если job есть, но падает на checkout или setup-python, отличайте инфраструктурную ошибку от провала прикладного утверждения. Если Python завершился с ошибкой импорта, проверьте реальные пути и зависимости. Если проблема только в содержании ответа, сохраните минимальный безопасный вход и точное различие ожидаемого и фактического полей.

В CI тестируется выбранный контракт, а не «качество ИИ вообще». Он не доказывает отсутствие галлюцинаций, безопасность всех возможных промптов или корректность каждой интеграции. Добавляйте отдельные фикстуры для отрицательных кейсов, пустого ввода, Unicode и граничных форматов, когда эти ситуации есть в вашем продукте. Для live API ограничивайте доступ к секрету и используйте отдельные тестовые данные. Не выводите заголовки Authorization, тело закрытого ответа или переменные окружения целиком.

Чек-лист перед слиянием

  • Workflow лежит в .github/workflows, а событие запуска соответствует задаче.
  • Скрипт работает локально на синтетическом входе и падает при намеренно нарушенном контракте.
  • Имена файлов, переменные окружения и пути в YAML совпадают с репозиторием.
  • Секреты не записаны в код, входные параметры, summary или диагностический файл.
  • Проверен успешный запуск и понятный неуспешный запуск; просмотрен конкретный job.
  • Код и тесты соответствуют ограничениям доверия для внешнего PR и правам токена.
  • Результат относится к актуальному коммиту, а не к предыдущей ревизии.

FAQ

Разве fork PR и так не получает секреты?

Политика GitHub ограничивает передачу секретов таким событиям, но проект не должен полагаться только на один барьер. Явно не передавайте ключи job и проверяйте контекст запуска.

Можно ли использовать pull_request_target?

Не запускайте непроверенный код головы PR в привилегированном контексте; это меняет границу доверия и требует отдельной архитектуры.

Зачем contents: read?

Тесту нужен код для checkout, но не право изменять репозиторий. Минимальный набор permissions снижает риск.

Как тестировать живой API?

Вынесите такой тест в ручной или плановый запуск на доверенной ветке, с отдельным ключом минимальных полномочий и синтетическими данными.

Как зафиксировать проверенный результат

После успешного запуска сохраните ссылку на конкретный run рядом с pull request: так ревьюер увидит проверенную ревизию. При следующем сбое сравнивайте не только цвет check, но и фикстуру, входные параметры, изменённые файлы и точный шаг ошибки. Если ослабили утверждение теста, объясните в PR, какой риск остался непокрытым, и добавьте отдельный сценарий. При переносе шаблона в другой репозиторий повторно проверьте структуру каталогов и разрешения: одинаковый YAML сам по себе не гарантирует одинаковую модель доверия. Не называйте демонстрационные результаты измерением качества или производительности AI-системы.

Официальные первоисточники

Проверено 19.09.2026. Описание синтаксиса и поведения: 1, 2, 3, 4. Читайте первоисточники при переносе шаблона в другой репозиторий: доступность версии action или поддерживаемый синтаксис могут измениться.

Читайте также

Комментарии

Пока тихо. Скажите первое слово